AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/11 22:36 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "check_func_args",
    "check_func_arg",
    "check_helper_call",
    "check_kfunc_call",
    "bpf_do_misc_fixups",
    "btf_check_func_arg_match",
    "process_arg_ptr_to_btf_id"
  ],
  "KMSANReasoning": "The patch series refactors the BPF verifier's argument-checking infrastructure by unifying helper, kfunc, and subprog argument verification paths into common functions (check_func_args, check_func_arg, check_func_arg_nullability, check_func_arg_release, etc.) and extending enum bpf_arg_type to encompass kfunc argument types. It also updates diagnostic formatting, skb packet access tracking for kfuncs, and misc fixups for prog_aux passing.\n\nAll stack-allocated and heap-allocated data structures introduced or modified in the patch (such as struct bpf_call_arg_meta, struct bpf_func_proto, and helper/kfunc descriptor records) are explicitly initialized using memset() or field assignments prior to access. The changes do not introduce or alter any kernel data copied to user space, netlink messages, socket options, or ioctl outputs. Nor do they introduce conditional branches on uninitialized kernel variables or expose uninitialized buffer regions to reads.\n\nPotential bugs introduced by this refactoring would be verifier logic errors (such as register type mismatches, safety checks, or potential out-of-bounds packet/memory accesses in BPF programs), which are detectable by KASAN, static analysis, or standard verifier test suites. Because there are no uninitialized memory access risks or info-leaks specific to KMSAN, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies core BPF verifier logic, unifying argument verification for BPF helpers and kfuncs into check_func_args() and check_func_arg(). It refactors register type checking, nullability and release checks, packet data access restrictions, and instruction fixups in bpf_do_misc_fixups(). This reachable logic in kernel/bpf/ directly impacts program verification and execution during BPF_PROG_LOAD syscalls, warranting fuzzing for potential safety regressions, verifier bugs, or assertion failures.",
  "WorthFuzzing": true
}

1/1 2026/09/11 22:36 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 2be0c3fa03e0a3b9e475d93a3ffc7b797c2ed327\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Sep 11 22:36:09 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex e80963971f680..d0066d744ceb9 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -874,7 +874,7 @@ enum bpf_type_flag {\n \n /* function argument constraints */\n enum bpf_arg_type {\n-\tARG_DONTCARE = 0,\t/* unused argument in helper function */\n+\tARG_UNUSED = 0,\t\t/* unused argument; terminates argument iteration */\n \n \t/* the following constraints used to prototype\n \t * bpf_map_lookup/update/delete_elem() functions\n@@ -909,6 +909,22 @@ enum bpf_arg_type {\n \tARG_PTR_TO_TIMER,\t/* pointer to bpf_timer */\n \tARG_KPTR_XCHG_DEST,\t/* pointer to destination that kptrs are bpf_kptr_xchg'd into */\n \tARG_PTR_TO_DYNPTR,      /* pointer to bpf_dynptr. See bpf_type_flag for dynptr type */\n+\n+\tARG_CONST_SCALAR,\t/* scalar known at verification time */\n+\tARG_CONST_MEM_SIZE,\t/* ARG_MEM_SIZE that must be constant */\n+\tARG_PTR_TO_ALLOC_BTF_ID,\t/* pointer to an allocated object */\n+\tARG_PTR_TO_REFCOUNTED_KPTR,\t/* pointer to a refcounted local kptr */\n+\tARG_PTR_TO_ITER,\t/* pointer to an iterator */\n+\tARG_PTR_TO_LIST_HEAD,\t/* pointer to bpf_list_head */\n+\tARG_PTR_TO_LIST_NODE,\t/* pointer to bpf_list_node */\n+\tARG_PTR_TO_RB_ROOT,\t/* pointer to bpf_rb_root */\n+\tARG_PTR_TO_RB_NODE,\t/* pointer to bpf_rb_node */\n+\tARG_PTR_TO_WORKQUEUE,\t/* pointer to bpf_wq */\n+\tARG_PTR_TO_TASK_WORK,\t/* pointer to bpf_task_work */\n+\tARG_PTR_TO_IRQ_FLAG,\t/* pointer to saved IRQ flags on the stack */\n+\tARG_PTR_TO_RES_SPIN_LOCK,\t/* pointer to bpf_res_spin_lock */\n+\tARG_PTR_TO_PROG_AUX,\t/* pointer to the caller's bpf_prog_aux */\n+\tARG_IGNORE,\t\t/* argument the verifier does not check at all */\n \t__BPF_ARG_TYPE_MAX,\n \n \t/* Extended arg_types. */\n@@ -1005,13 +1021,13 @@ struct bpf_func_proto {\n \t};\n \tunion {\n \t\tstruct {\n-\t\t\tu32 *arg1_btf_id;\n-\t\t\tu32 *arg2_btf_id;\n-\t\t\tu32 *arg3_btf_id;\n-\t\t\tu32 *arg4_btf_id;\n-\t\t\tu32 *arg5_btf_id;\n+\t\t\tconst u32 *arg1_btf_id;\n+\t\t\tconst u32 *arg2_btf_id;\n+\t\t\tconst u32 *arg3_btf_id;\n+\t\t\tconst u32 *arg4_btf_id;\n+\t\t\tconst u32 *arg5_btf_id;\n \t\t};\n-\t\tu32 *arg_btf_id[MAX_BPF_FUNC_ARGS];\n+\t\tconst u32 *arg_btf_id[MAX_BPF_FUNC_ARGS];\n \t\tstruct {\n \t\t\tsize_t arg1_size;\n \t\t\tsize_t arg2_size;\ndiff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 9727df5af83ab..1e7593e8d5c5d 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -1591,7 +1591,7 @@ struct bpf_call_arg_meta {\n \t * verification logic\n \t *   bpf_obj_drop/bpf_percpu_obj_drop\n \t *     Record the local kptr type to be drop'd\n-\t *   bpf_refcount_acquire (via KF_ARG_PTR_TO_REFCOUNTED_KPTR arg type)\n+\t *   bpf_refcount_acquire (via ARG_PTR_TO_REFCOUNTED_KPTR arg type)\n \t *     Record the local kptr type to be refcount_incr'd and use\n \t *     arg_owning_ref to determine whether refcount_acquire should be\n \t *     fallible\n@@ -1599,7 +1599,6 @@ struct bpf_call_arg_meta {\n \tstruct btf *arg_btf;\n \tu32 arg_btf_id;\n \tbool arg_owning_ref;\n-\tbool arg_prog;\n \n \tstruct {\n \t\tstruct btf_field *field;\ndiff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c\nindex 31057c8f3a7c2..122a4101ce944 100644\n--- a/kernel/bpf/btf.c\n+++ b/kernel/bpf/btf.c\n@@ -8244,7 +8244,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)\n \t\t\treturn -EINVAL;\n \t\t}\n \t\tif (btf_type_is_int(t) || btf_is_any_enum(t)) {\n-\t\t\tsub-\u003eargs[i].arg_type = ARG_ANYTHING;\n+\t\t\tsub-\u003eargs[i].arg_type = ARG_SCALAR;\n \t\t\tcontinue;\n \t\t}\n \t\tif (!is_global)\ndiff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c\nindex 0abbbe177e317..a2cac59c66391 100644\n--- a/kernel/bpf/diagnostics.c\n+++ b/kernel/bpf/diagnostics.c\n@@ -960,6 +960,35 @@ const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_t\n \t}\n }\n \n+const char *bpf_diag_arg_type_plain(enum bpf_arg_type type)\n+{\n+\tswitch (base_type(type)) {\n+\tcase ARG_MEM_SIZE:\n+\tcase ARG_CONST_MEM_SIZE:\n+\t\treturn \"an integer scalar length for this memory argument\";\n+\tcase ARG_PTR_TO_CTX:\n+\t\treturn \"the original program context pointer or preserve it before modifying registers\";\n+\tcase ARG_SCALAR:\n+\tcase ARG_CONST_SCALAR:\n+\tcase ARG_CONST_ALLOC_SIZE_OR_ZERO:\n+\t\treturn \"an integer scalar value for this argument, not a pointer or resource object\";\n+\tcase ARG_PTR_TO_CONST_STR:\n+\t\treturn \"a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value\";\n+\tcase ARG_PTR_TO_DYNPTR:\n+\t\treturn \"the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path\";\n+\tcase ARG_PTR_TO_ALLOC_BTF_ID:\n+\t\treturn \"a pointer returned by the matching BPF object allocation path\";\n+\tcase ARG_PTR_TO_REFCOUNTED_KPTR:\n+\t\treturn \"an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field\";\n+\tcase ARG_PTR_TO_ITER:\n+\t\treturn \"the address of a stack iterator object for iterator new, next, and destroy calls\";\n+\tcase ARG_PTR_TO_IRQ_FLAG:\n+\t\treturn \"the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave()\";\n+\tdefault:\n+\t\treturn \"a value with one of the accepted pointer or scalar types for this call\";\n+\t}\n+}\n+\n static const char *diag_arg_ordinal(int argno)\n {\n \tswitch (argno) {\ndiff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h\nindex d1b79945008a8..a4102fb049ece 100644\n--- a/kernel/bpf/diagnostics.h\n+++ b/kernel/bpf/diagnostics.h\n@@ -51,6 +51,7 @@ const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list\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+const char *bpf_diag_arg_type_plain(enum bpf_arg_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);\ndiff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c\nindex fcf68cfb91e91..2add8001c3ec3 100644\n--- a/kernel/bpf/fixups.c\n+++ b/kernel/bpf/fixups.c\n@@ -2020,7 +2020,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)\n \t\t\tgoto next_insn;\n \t\t}\n \n-\t\tif (insn-\u003eimm == BPF_FUNC_timer_set_callback) {\n+\t\taux = \u0026env-\u003einsn_aux_data[i + delta];\n+\t\tif (aux-\u003earg_prog) {\n \t\t\t/* The verifier will process callback_fn as many times as necessary\n \t\t\t * with different maps and the register states prepared by\n \t\t\t * set_timer_callback_state will be accurate.\n@@ -2035,7 +2036,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)\n \t\t\t *     bpf_timer_set_callback-ed will return -EINVAL.\n \t\t\t */\n \t\t\tstruct bpf_insn ld_addrs[2] = {\n-\t\t\t\tBPF_LD_IMM64(BPF_REG_3, (long)prog-\u003eaux),\n+\t\t\t\tBPF_LD_IMM64(aux-\u003earg_prog, (long)prog-\u003eaux),\n \t\t\t};\n \n \t\t\tinsn_buf[0] = ld_addrs[0];\ndiff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c\nindex b3cc5c8fc8756..051b6654e57c6 100644\n--- a/kernel/bpf/helpers.c\n+++ b/kernel/bpf/helpers.c\n@@ -1510,6 +1510,7 @@ static const struct bpf_func_proto bpf_timer_set_callback_proto = {\n \t.ret_type\t= RET_INTEGER,\n \t.arg1_type\t= ARG_PTR_TO_TIMER,\n \t.arg2_type\t= ARG_PTR_TO_FUNC,\n+\t.arg3_type\t= ARG_PTR_TO_PROG_AUX,\n };\n \n static bool defer_timer_wq_op(void)\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 9e79750e24808..617a277c3558c 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -960,8 +960,12 @@ static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct\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/*\n+\t * ARG_PTR_TO_DYNPTR without a type flag takes any type of dynptr.\n+\t * Test the flags rather than the whole arg_type, which may carry\n+\t * unrelated ones such as PTR_MAYBE_NULL.\n+\t */\n+\tif (!(arg_type \u0026 DYNPTR_TYPE_FLAG_MASK))\n \t\treturn true;\n \n \treturn dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);\n@@ -4877,7 +4881,7 @@ static int check_map_access(struct bpf_verifier_env *env, struct bpf_reg_state *\n }\n \n static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,\n-\t\t\t       const struct bpf_func_proto *fn,\n+\t\t\t       const struct bpf_call_arg_meta *meta,\n \t\t\t       enum bpf_access_type t)\n {\n \tenum bpf_prog_type prog_type = resolve_prog_type(env-\u003eprog);\n@@ -4901,10 +4905,11 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,\n \tcase BPF_PROG_TYPE_LWT_XMIT:\n \tcase BPF_PROG_TYPE_SK_SKB:\n \tcase BPF_PROG_TYPE_SK_MSG:\n-\t\tif (fn)\n-\t\t\treturn fn-\u003epkt_access;\n+\t\tif (meta \u0026\u0026 !meta-\u003ebtf \u0026\u0026 meta-\u003efunc_id)\n+\t\t\treturn meta-\u003efn-\u003epkt_access;\n \n-\t\tenv-\u003eseen_direct_write = true;\n+\t\tif (t == BPF_WRITE)\n+\t\t\tenv-\u003eseen_direct_write = true;\n \t\treturn true;\n \n \tcase BPF_PROG_TYPE_CGROUP_SOCKOPT:\n@@ -5162,18 +5167,6 @@ static u32 *reg2btf_ids[__BPF_REG_TYPE_MAX] = {\n \t[CONST_PTR_TO_MAP] = btf_bpf_map_id,\n };\n \n-static enum bpf_reg_type lookup_reg2btf_ids(u32 ref_id)\n-{\n-\tenum bpf_reg_type type;\n-\n-\tfor (type = 0; type \u003c __BPF_REG_TYPE_MAX; type++) {\n-\t\tif (reg2btf_ids[type] \u0026\u0026 *reg2btf_ids[type] == ref_id)\n-\t\t\treturn type;\n-\t}\n-\n-\treturn NOT_INIT;\n-}\n-\n static bool is_trusted_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)\n {\n \t/* A referenced register is always trusted. */\n@@ -7103,6 +7096,10 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_\n \tswitch (base_type(reg-\u003etype)) {\n \tcase PTR_TO_PACKET:\n \tcase PTR_TO_PACKET_META:\n+\t\tif (!may_access_direct_pkt_data(env, meta, access_type)) {\n+\t\t\tverbose(env, \"function access to the packet is not allowed\\n\");\n+\t\t\treturn -EACCES;\n+\t\t}\n \t\treturn check_packet_access(env, reg, argno, 0, access_size,\n \t\t\t\t\t   zero_size_allowed);\n \tcase PTR_TO_MAP_KEY:\n@@ -7219,7 +7216,7 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,\n \t * the memory that the helper could just partially fill up.\n \t */\n \tif (!tnum_is_const(size_reg-\u003evar_off))\n-\t\tmeta = NULL;\n+\t\tmeta-\u003earg_raw_mem.regno = 0;\n \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@@ -7663,11 +7660,11 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u\n /*\n  * Validate dynptr arguments for helper, kfunc and subprog.\n  *\n- * @dynptr is both input and output. It is populated when the argument is\n- * tagged with MEM_UNINIT (i.e., the dynptr argument that will be constructed)\n- * and consumed when the argument is expecting to be an initialized dynptr.\n- * @parent_id is used to track the referenced parent object (e.g., file or skb in\n- * qdisc program) when constructing a dynptr.\n+ * @meta carries the dynptr and referenced-object state. The dynptr is populated\n+ * when the argument is tagged with MEM_UNINIT (i.e., the dynptr argument that\n+ * will be constructed) and consumed when the argument is expected to be an\n+ * initialized dynptr. The reference tracks the parent object (e.g., file or skb\n+ * in qdisc program) when constructing a dynptr.\n  *\n  * There are two register types representing a bpf_dynptr, one is PTR_TO_STACK\n  * which points to a stack slot, and the other is CONST_PTR_TO_DYNPTR.\n@@ -7684,9 +7681,8 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u\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, 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+\t\t\t       argno_t argno, int insn_idx, enum bpf_arg_type arg_type,\n+\t\t\t       struct bpf_call_arg_meta *meta)\n {\n \tint spi, err = 0;\n \n@@ -7695,7 +7691,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\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\tenv, insn_idx, argno, meta-\u003efunc_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@@ -7723,7 +7719,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\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\"This function 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@@ -7736,7 +7732,8 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\n \t\t\t\treturn err;\n \t\t}\n \n-\t\terr = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx, ref_obj, dynptr);\n+\t\terr = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx,\n+\t\t\t\t\t      \u0026meta-\u003eref_obj, \u0026meta-\u003edynptr);\n \t} else /* OBJ_RELEASE and None case from above */ {\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@@ -7766,7 +7763,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\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\tenv, insn_idx, argno, meta-\u003efunc_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@@ -7785,11 +7782,9 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat\n \t\t\treg = \u0026state-\u003estack[spi].spilled_ptr;\n \t\t}\n \n-\t\tif (dynptr) {\n-\t\t\tdynptr-\u003etype = reg-\u003edynptr.type;\n-\t\t\tdynptr-\u003eid = reg-\u003eid;\n-\t\t\tdynptr-\u003eparent_id = reg-\u003eparent_id;\n-\t\t}\n+\t\tmeta-\u003edynptr.type = reg-\u003edynptr.type;\n+\t\tmeta-\u003edynptr.id = reg-\u003eid;\n+\t\tmeta-\u003edynptr.parent_id = reg-\u003eparent_id;\n \t}\n \treturn err;\n }\n@@ -7853,8 +7848,8 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\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\t\t\"the function 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 function.\");\n \t\treturn -EINVAL;\n \t}\n \tt = btf_type_by_id(meta-\u003ebtf, btf_id);\n@@ -8178,9 +8173,43 @@ static bool arg_type_is_dynptr(enum bpf_arg_type type)\n \treturn base_type(type) == ARG_PTR_TO_DYNPTR;\n }\n \n+/*\n+ * An argument that only ever takes a scalar, so a zero register passed to it\n+ * is a value rather than a NULL pointer.\n+ */\n+static bool arg_type_is_scalar(enum bpf_arg_type type)\n+{\n+\tswitch (base_type(type)) {\n+\tcase ARG_SCALAR:\n+\tcase ARG_CONST_SCALAR:\n+\tcase ARG_MEM_SIZE:\n+\tcase ARG_MEM_SIZE_OR_ZERO:\n+\tcase ARG_CONST_MEM_SIZE:\n+\tcase ARG_CONST_ALLOC_SIZE_OR_ZERO:\n+\t\treturn true;\n+\tdefault:\n+\t\treturn false;\n+\t}\n+}\n+\n+/*\n+ * A kfunc is named by a BTF ID, which can take the same numeric value as an\n+ * enum bpf_func_id. Only test meta-\u003efunc_id against a BPF_FUNC_* once the call\n+ * is known to be to a helper; meta-\u003ebtf is set only for a kfunc.\n+ */\n+static bool is_helper_call(const struct bpf_call_arg_meta *meta, enum bpf_func_id func_id)\n+{\n+\treturn !meta-\u003ebtf \u0026\u0026 meta-\u003efunc_id == func_id;\n+}\n+\n+static bool is_kfunc_call(const struct bpf_call_arg_meta *meta, u32 btf_id)\n+{\n+\treturn meta-\u003ebtf \u0026\u0026 meta-\u003efunc_id == btf_id;\n+}\n+\n static int resolve_map_arg_type(struct bpf_verifier_env *env,\n-\t\t\t\t const struct bpf_call_arg_meta *meta,\n-\t\t\t\t enum bpf_arg_type *arg_type)\n+\t\t\t\tconst struct bpf_call_arg_meta *meta,\n+\t\t\t\tenum bpf_arg_type *arg_type)\n {\n \tif (!meta-\u003emap.ptr) {\n \t\t/* kernel subsystem misconfigured verifier */\n@@ -8199,7 +8228,7 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,\n \t\t}\n \t\tbreak;\n \tcase BPF_MAP_TYPE_BLOOM_FILTER:\n-\t\tif (meta-\u003efunc_id == BPF_FUNC_map_peek_elem)\n+\t\tif (is_helper_call(meta, BPF_FUNC_map_peek_elem))\n \t\t\t*arg_type = ARG_PTR_TO_MAP_VALUE;\n \t\tbreak;\n \tdefault:\n@@ -8208,6 +8237,48 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,\n \treturn 0;\n }\n \n+static int resolve_func_arg_type(struct bpf_verifier_env *env,\n+\t\t\t\t struct bpf_reg_state *reg, u32 arg,\n+\t\t\t\t struct bpf_call_arg_meta *meta,\n+\t\t\t\t enum bpf_arg_type *arg_type, u32 *arg_size);\n+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n+\t\t\t\t     argno_t argno, enum bpf_arg_type arg_type,\n+\t\t\t\t     const struct btf *arg_btf, u32 arg_btf_id,\n+\t\t\t\t     struct bpf_call_arg_meta *meta, int insn_idx);\n+static bool is_kfunc_arg_nonown_allowed(const struct btf *btf,\n+\t\t\t\t\tconst struct btf_param *arg);\n+static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,\n+\t\t\t\t\t  const struct btf_param *arg,\n+\t\t\t\t\t  const char *name);\n+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta);\n+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta);\n+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta);\n+static bool is_bpf_obj_drop_kfunc(u32 func_id);\n+static bool is_bpf_percpu_obj_drop_kfunc(u32 func_id);\n+static bool is_bpf_rbtree_add_kfunc(u32 func_id);\n+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta);\n+static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env);\n+static int process_irq_flag(struct bpf_verifier_env *env,\n+\t\t\t    struct bpf_reg_state *reg, argno_t argno,\n+\t\t\t    struct bpf_call_arg_meta *meta);\n+static int process_kf_arg_ptr_to_list_head(struct bpf_verifier_env *env,\n+\t\t\t\t\t   struct bpf_reg_state *reg,\n+\t\t\t\t\t   argno_t argno,\n+\t\t\t\t\t   struct bpf_call_arg_meta *meta);\n+static int process_kf_arg_ptr_to_rbtree_root(struct bpf_verifier_env *env,\n+\t\t\t\t\t     struct bpf_reg_state *reg,\n+\t\t\t\t\t     argno_t argno,\n+\t\t\t\t\t     struct bpf_call_arg_meta *meta);\n+static int process_kf_arg_ptr_to_list_node(struct bpf_verifier_env *env,\n+\t\t\t\t\t   struct bpf_reg_state *reg,\n+\t\t\t\t\t   argno_t argno,\n+\t\t\t\t\t   struct bpf_call_arg_meta *meta);\n+static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,\n+\t\t\t\t\t     struct bpf_reg_state *reg,\n+\t\t\t\t\t     argno_t argno,\n+\t\t\t\t\t     struct bpf_call_arg_meta *meta);\n+static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env);\n+\n struct bpf_reg_types {\n \tconst enum bpf_reg_type types[10];\n \tu32 *btf_id;\n@@ -8251,7 +8322,7 @@ static const struct bpf_reg_types mem_types = {\n \t},\n };\n \n-static const struct bpf_reg_types spin_lock_types = {\n+static const struct bpf_reg_types map_value_or_alloc_obj_types = {\n \t.types = {\n \t\tPTR_TO_MAP_VALUE,\n \t\tPTR_TO_BTF_ID | MEM_ALLOC,\n@@ -8280,7 +8351,30 @@ static const struct bpf_reg_types percpu_btf_ptr_types = {\n static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };\n static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };\n static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };\n-static const struct bpf_reg_types timer_types = { .types = { PTR_TO_MAP_VALUE } };\n+static const struct bpf_reg_types map_value_types = { .types = { PTR_TO_MAP_VALUE } };\n+static const struct bpf_reg_types arena_types = {\n+\t.types = {\n+\t\tPTR_TO_ARENA,\n+\t\tSCALAR_VALUE,\n+\t}\n+};\n+\n+static const struct bpf_reg_types alloc_obj_drop_types = {\n+\t.types = {\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC,\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU,\n+\t}\n+};\n+\n+static const struct bpf_reg_types alloc_obj_types = {\n+\t.types = {\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC,\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC | MEM_RCU,\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF,\n+\t\tPTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU,\n+\t}\n+};\n+\n static const struct bpf_reg_types kptr_xchg_dest_types = {\n \t.types = {\n \t\tPTR_TO_MAP_VALUE,\n@@ -8311,16 +8405,30 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {\n #endif\n \t[ARG_PTR_TO_SOCKET]\t\t= \u0026fullsock_types,\n \t[ARG_PTR_TO_BTF_ID]\t\t= \u0026btf_ptr_types,\n-\t[ARG_PTR_TO_SPIN_LOCK]\t\t= \u0026spin_lock_types,\n+\t[ARG_PTR_TO_SPIN_LOCK]\t\t= \u0026map_value_or_alloc_obj_types,\n \t[ARG_PTR_TO_MEM]\t\t= \u0026mem_types,\n \t[ARG_PTR_TO_RINGBUF_MEM]\t= \u0026ringbuf_mem_types,\n \t[ARG_PTR_TO_PERCPU_BTF_ID]\t= \u0026percpu_btf_ptr_types,\n \t[ARG_PTR_TO_FUNC]\t\t= \u0026func_ptr_types,\n \t[ARG_PTR_TO_STACK]\t\t= \u0026stack_ptr_types,\n \t[ARG_PTR_TO_CONST_STR]\t\t= \u0026const_str_ptr_types,\n-\t[ARG_PTR_TO_TIMER]\t\t= \u0026timer_types,\n+\t[ARG_PTR_TO_TIMER]\t\t= \u0026map_value_types,\n \t[ARG_KPTR_XCHG_DEST]\t\t= \u0026kptr_xchg_dest_types,\n \t[ARG_PTR_TO_DYNPTR]\t\t= \u0026dynptr_types,\n+\t[ARG_CONST_SCALAR]\t\t= \u0026scalar_types,\n+\t[ARG_CONST_MEM_SIZE]\t\t= \u0026scalar_types,\n+\t[ARG_PTR_TO_ALLOC_BTF_ID]\t= \u0026alloc_obj_drop_types,\n+\t[ARG_PTR_TO_REFCOUNTED_KPTR]\t= \u0026alloc_obj_types,\n+\t[ARG_PTR_TO_ITER]\t\t= \u0026stack_ptr_types,\n+\t[ARG_PTR_TO_LIST_HEAD]\t\t= \u0026map_value_or_alloc_obj_types,\n+\t[ARG_PTR_TO_LIST_NODE]\t\t= \u0026alloc_obj_types,\n+\t[ARG_PTR_TO_RB_ROOT]\t\t= \u0026map_value_or_alloc_obj_types,\n+\t[ARG_PTR_TO_RB_NODE]\t\t= \u0026alloc_obj_types,\n+\t[ARG_PTR_TO_RES_SPIN_LOCK]\t= \u0026map_value_or_alloc_obj_types,\n+\t[ARG_PTR_TO_WORKQUEUE]\t\t= \u0026map_value_types,\n+\t[ARG_PTR_TO_TASK_WORK]\t\t= \u0026map_value_types,\n+\t[ARG_PTR_TO_IRQ_FLAG]\t\t= \u0026stack_ptr_types,\n+\t[ARG_PTR_TO_ARENA]\t\t= \u0026arena_types,\n };\n \n static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,\n@@ -8360,6 +8468,71 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u\n \tbpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);\n }\n \n+static int check_func_arg_nullability(struct bpf_verifier_env *env,\n+\t\t\t\t      struct bpf_reg_state *reg, argno_t argno,\n+\t\t\t\t      enum bpf_arg_type arg_type,\n+\t\t\t\t      struct bpf_call_arg_meta *meta, int insn_idx)\n+{\n+\tconst char *expected_type = \"pointer\";\n+\n+\tif (arg_type_is_scalar(arg_type) || type_may_be_null(arg_type) ||\n+\t    (!bpf_register_is_null(reg) \u0026\u0026 !type_may_be_null(reg-\u003etype)))\n+\t\treturn 0;\n+\n+\tif (meta-\u003ebtf) {\n+\t\tu32 arg_btf_id;\n+\n+\t\targ_btf_id = btf_params(meta-\u003efunc_proto)[arg_idx_from_argno(argno)].type;\n+\t\texpected_type = bpf_diag_fmt(env, \"value of type %s\",\n+\t\t\t\t\t     bpf_diag_fmt_btf_type(env, meta-\u003ebtf, arg_btf_id));\n+\t}\n+\n+\tverbose(env, \"Possibly NULL pointer passed to trusted %s\\n\",\n+\t\treg_arg_name(env, argno));\n+\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t      \"Add a NULL check and make the call only on the non-NULL path.\",\n+\t\t\t      \"the pointer may be NULL, but this call requires a non-NULL %s\",\n+\t\t\t      expected_type);\n+\treturn -EACCES;\n+}\n+\n+static int check_func_arg_release(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n+\t\t\t\t  argno_t argno, enum bpf_arg_type arg_type,\n+\t\t\t\t  struct bpf_call_arg_meta *meta, int insn_idx)\n+{\n+\tconst char *expected_type = \"pointer\";\n+\n+\tif (!arg_type_is_release(arg_type))\n+\t\treturn 0;\n+\n+\tif (arg_type_is_dynptr(arg_type) || reg_is_referenced(env, reg) ||\n+\t    bpf_register_is_null(reg))\n+\t\treturn 0;\n+\n+\tverbose(env, \"release function %s expects referenced PTR_TO_BTF_ID passed to %s\\n\",\n+\t\tmeta-\u003efunc_name, reg_arg_name(env, argno));\n+\n+\tif (meta-\u003ebtf) {\n+\t\tconst struct btf_param *btf_arg;\n+\t\tconst struct btf_type *t;\n+\t\tu32 ref_id;\n+\n+\t\tbtf_arg = \u0026btf_params(meta-\u003efunc_proto)[arg_idx_from_argno(argno)];\n+\t\tref_id = btf_arg-\u003etype;\n+\t\tt = btf_type_skip_modifiers(meta-\u003ebtf, btf_arg-\u003etype, NULL);\n+\t\tif (btf_type_is_ptr(t))\n+\t\t\tbtf_type_skip_modifiers(meta-\u003ebtf, t-\u003etype, \u0026ref_id);\n+\t\texpected_type = bpf_diag_fmt(env, \"value of type %s\",\n+\t\t\t\t\t     bpf_diag_fmt_btf_type(env, meta-\u003ebtf, ref_id));\n+\t}\n+\n+\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t      bpf_diag_fmt(env, \"Pass the resource-owning %s returned by the matching acquire call, or avoid the release function after ownership has already been transferred or released.\",\n+\t\t\t\t\t   expected_type),\n+\t\t\t      \"release functions require a value that owns a live resource returned by a matching acquire function\");\n+\treturn -EINVAL;\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@@ -8381,19 +8554,21 @@ static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,\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, const u32 *arg_btf_id,\n-\t\t\t  struct bpf_call_arg_meta *meta, const char *call_name)\n+\t\t\t  enum bpf_arg_type arg_type, struct bpf_call_arg_meta *meta)\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+\tint i, j;\n \n \tcompatible = compatible_reg_types[base_type(arg_type)];\n \tif (!compatible) {\n \t\tverifier_bug(env, \"unsupported arg type %d\", arg_type);\n \t\treturn -EFAULT;\n \t}\n+\tif (meta-\u003ebtf \u0026\u0026 base_type(arg_type) == ARG_PTR_TO_BTF_ID \u0026\u0026\n+\t    (base_type(type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(type)]))\n+\t\tgoto found;\n \n \t/* ARG_PTR_TO_MEM + RDONLY is compatible with PTR_TO_MEM and PTR_TO_MEM + RDONLY,\n \t * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM and NOT with PTR_TO_MEM + RDONLY\n@@ -8413,9 +8588,14 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re\n \t\ttype \u0026= ~PTR_MAYBE_NULL;\n \tif (base_type(arg_type) == ARG_PTR_TO_MEM)\n \t\ttype \u0026= ~DYNPTR_TYPE_FLAG_MASK;\n+\t/* Allow allocated memory for kfunc ARG_PTR_TO_MEM but not helper. */\n+\tif (meta-\u003ebtf \u0026\u0026 base_type(arg_type) == ARG_PTR_TO_MEM \u0026\u0026\n+\t    type_is_ptr_alloc_obj(type))\n+\t\ttype = PTR_TO_MEM;\n \n \t/* Local kptr types are allowed as the source argument of bpf_kptr_xchg */\n-\tif (meta-\u003efunc_id == BPF_FUNC_kptr_xchg \u0026\u0026 type_is_alloc(type) \u0026\u0026 reg_from_argno(argno) == BPF_REG_2) {\n+\tif (is_helper_call(meta, BPF_FUNC_kptr_xchg) \u0026\u0026 type_is_alloc(type) \u0026\u0026\n+\t    reg_from_argno(argno) == BPF_REG_2) {\n \t\ttype \u0026= ~MEM_ALLOC;\n \t\ttype \u0026= ~MEM_PERCPU;\n \t}\n@@ -8435,115 +8615,13 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re\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+\tbpf_diag_call_arg_fmt(env, env-\u003einsn_idx, argno, meta-\u003efunc_name,\n+\t\t\t      bpf_diag_fmt(env, \"Pass %s.\", bpf_diag_arg_type_plain(arg_type)),\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-\tif (base_type(reg-\u003etype) != PTR_TO_BTF_ID)\n-\t\treturn 0;\n-\n-\tif (compatible == \u0026mem_types) {\n-\t\tif (!(arg_type \u0026 MEM_RDONLY)) {\n-\t\t\tverbose(env,\n-\t\t\t\t\"%s() may write into memory pointed by %s type=%s\\n\",\n-\t\t\t\tfunc_id_name(meta-\u003efunc_id),\n-\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n-\t\t\treturn -EACCES;\n-\t\t}\n-\t\treturn 0;\n-\t}\n-\n-\tswitch ((int)reg-\u003etype) {\n-\tcase PTR_TO_BTF_ID:\n-\tcase PTR_TO_BTF_ID | PTR_TRUSTED:\n-\tcase PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL:\n-\tcase PTR_TO_BTF_ID | MEM_RCU:\n-\tcase PTR_TO_BTF_ID | PTR_MAYBE_NULL:\n-\tcase PTR_TO_BTF_ID | PTR_MAYBE_NULL | MEM_RCU:\n-\t{\n-\t\t/* For bpf_sk_release, it needs to match against first member\n-\t\t * 'struct sock_common', hence make an exception for it. This\n-\t\t * allows bpf_sk_release to work for multiple socket types.\n-\t\t */\n-\t\tbool strict_type_match = arg_type_is_release(arg_type) \u0026\u0026\n-\t\t\t\t\t meta-\u003efunc_id != BPF_FUNC_sk_release;\n-\n-\t\tif (type_may_be_null(reg-\u003etype) \u0026\u0026\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-\t\tif (!arg_btf_id) {\n-\t\t\tif (!compatible-\u003ebtf_id) {\n-\t\t\t\tverifier_bug(env, \"missing arg compatible BTF ID\");\n-\t\t\t\treturn -EFAULT;\n-\t\t\t}\n-\t\t\targ_btf_id = compatible-\u003ebtf_id;\n-\t\t}\n-\n-\t\tif (meta-\u003efunc_id == BPF_FUNC_kptr_xchg) {\n-\t\t\tif (map_kptr_match_type(env, meta-\u003ekptr_field, reg, reg_from_argno(argno)))\n-\t\t\t\treturn -EACCES;\n-\t\t} else {\n-\t\t\tif (arg_btf_id == BPF_PTR_POISON) {\n-\t\t\t\tverbose(env, \"verifier internal error:\");\n-\t\t\t\tverbose(env, \"%s has non-overwritten BPF_PTR_POISON type\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EACCES;\n-\t\t\t}\n-\n-\t\t\terr = __check_ptr_off_reg(env, reg, argno, true);\n-\t\t\tif (err)\n-\t\t\t\treturn err;\n-\n-\t\t\tif (!btf_struct_ids_match(\u0026env-\u003elog, reg-\u003ebtf, reg-\u003ebtf_id,\n-\t\t\t\t\t\t  reg-\u003evar_off.value, btf_vmlinux, *arg_btf_id,\n-\t\t\t\t\t\t  strict_type_match, !type_is_alloc(reg-\u003etype))) {\n-\t\t\t\tverbose(env, \"%s is of type %s but %s is expected\\n\",\n-\t\t\t\t\treg_arg_name(env, argno),\n-\t\t\t\t\tbtf_type_name(reg-\u003ebtf, reg-\u003ebtf_id),\n-\t\t\t\t\tbtf_type_name(btf_vmlinux, *arg_btf_id));\n-\t\t\t\treturn -EACCES;\n-\t\t\t}\n-\t\t}\n-\t\tbreak;\n-\t}\n-\tcase PTR_TO_BTF_ID | MEM_ALLOC:\n-\tcase PTR_TO_BTF_ID | MEM_PERCPU | MEM_ALLOC:\n-\tcase PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:\n-\tcase PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:\n-\t\tif (meta-\u003efunc_id != BPF_FUNC_spin_lock \u0026\u0026 meta-\u003efunc_id != BPF_FUNC_spin_unlock \u0026\u0026\n-\t\t    meta-\u003efunc_id != BPF_FUNC_kptr_xchg) {\n-\t\t\tverifier_bug(env, \"unimplemented handling of MEM_ALLOC\");\n-\t\t\treturn -EFAULT;\n-\t\t}\n-\t\t/* Check if local kptr in src arg matches kptr in dst arg */\n-\t\tif (meta-\u003efunc_id == BPF_FUNC_kptr_xchg) {\n-\t\t\tint regno = reg_from_argno(argno);\n-\n-\t\t\tif (regno == BPF_REG_2 \u0026\u0026\n-\t\t\t    map_kptr_match_type(env, meta-\u003ekptr_field, reg, regno))\n-\t\t\t\treturn -EACCES;\n-\t\t}\n-\t\tbreak;\n-\tcase PTR_TO_BTF_ID | MEM_PERCPU:\n-\tcase PTR_TO_BTF_ID | MEM_PERCPU | MEM_RCU:\n-\tcase PTR_TO_BTF_ID | MEM_PERCPU | PTR_TRUSTED:\n-\t\t/* Handled by helper specific checks */\n-\t\tbreak;\n-\tdefault:\n-\t\tverifier_bug(env, \"invalid PTR_TO_BTF_ID register for type match\");\n-\t\treturn -EFAULT;\n-\t}\n \treturn 0;\n }\n \n@@ -8564,10 +8642,9 @@ reg_find_field_offset(const struct bpf_reg_state *reg, s32 off, u32 fields)\n \treturn field;\n }\n \n-static int __check_func_arg_reg_off(struct bpf_verifier_env *env,\n-\t\t\t\t    const struct bpf_reg_state *reg, argno_t argno,\n-\t\t\t\t    enum bpf_arg_type arg_type,\n-\t\t\t\t    bool btf_id_fixed_off_ok)\n+static int check_func_arg_reg_off(struct bpf_verifier_env *env,\n+\t\t\t\t  const struct bpf_reg_state *reg, argno_t argno,\n+\t\t\t\t  enum bpf_arg_type arg_type)\n {\n \tu32 type = reg-\u003etype;\n \n@@ -8623,12 +8700,15 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,\n \tcase PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:\n \tcase PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:\n \t\t/* When referenced PTR_TO_BTF_ID is passed to release function,\n-\t\t * its fixed offset must be 0. In the other cases, fixed offset\n-\t\t * can be non-zero unless the caller requires otherwise.\n-\t\t * var_off always must be 0 for PTR_TO_BTF_ID, hence we still\n-\t\t * need to do checks instead of returning.\n+\t\t * its fixed offset must be 0. bpf_refcount_acquire() returns the\n+\t\t * pointer it was given while incrementing the refcount at the\n+\t\t * refcount field offset, so it needs a zero offset too. In the\n+\t\t * other cases, fixed offset can be non-zero. var_off always must\n+\t\t * be 0 for PTR_TO_BTF_ID, hence we still need to do checks\n+\t\t * instead of returning.\n \t\t */\n-\t\treturn __check_ptr_off_reg(env, reg, argno, btf_id_fixed_off_ok);\n+\t\treturn __check_ptr_off_reg(env, reg, argno,\n+\t\t\t\t\t   base_type(arg_type) != ARG_PTR_TO_REFCOUNTED_KPTR);\n \tcase PTR_TO_CTX:\n \t\t/*\n \t\t * Allow fixed and variable offsets for syscall context, but\n@@ -8636,7 +8716,7 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,\n \t\t * otherwise we may get modified ctx in tail called programs and\n \t\t * global subprogs (that may act as extension prog hooks).\n \t\t */\n-\t\tif (arg_type != ARG_PTR_TO_CTX \u0026\u0026 is_var_ctx_off_allowed(env-\u003eprog))\n+\t\tif (base_type(arg_type) != ARG_PTR_TO_CTX \u0026\u0026 is_var_ctx_off_allowed(env-\u003eprog))\n \t\t\treturn 0;\n \t\tfallthrough;\n \tdefault:\n@@ -8644,13 +8724,6 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,\n \t}\n }\n \n-static int check_func_arg_reg_off(struct bpf_verifier_env *env,\n-\t\t\t\t  const struct bpf_reg_state *reg, argno_t argno,\n-\t\t\t\t  enum bpf_arg_type arg_type)\n-{\n-\treturn __check_func_arg_reg_off(env, reg, argno, arg_type, true);\n-}\n-\n static int check_arg_const_str(struct bpf_verifier_env *env,\n \t\t\t       struct bpf_reg_state *reg, argno_t argno)\n {\n@@ -8816,61 +8889,58 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t\t  struct bpf_call_arg_meta *meta,\n \t\t\t  int insn_idx)\n {\n+\tconst struct btf_param *btf_arg = meta-\u003ebtf ? \u0026btf_params(meta-\u003efunc_proto)[arg] : NULL;\n \tconst struct bpf_func_proto *fn = meta-\u003efn;\n-\tu32 regno = BPF_REG_1 + arg;\n-\tstruct bpf_reg_state *reg = reg_state(env, regno);\n+\tstruct bpf_func_state *caller = cur_func(env);\n+\tstruct bpf_reg_state *regs = cur_regs(env);\n+\targno_t argno = argno_from_arg(arg + 1);\n+\tstruct bpf_reg_state *reg = get_func_arg_reg(caller, regs, arg);\n \tenum bpf_arg_type arg_type = fn-\u003earg_type[arg];\n-\targno_t argno = argno_from_reg(regno);\n-\tenum bpf_reg_type type = reg-\u003etype;\n-\tu32 *arg_btf_id = NULL;\n+\tint regno = reg_from_argno(argno);\n+\tu32 arg_size = arg_type \u0026 MEM_FIXED_SIZE ? fn-\u003earg_size[arg] : 0;\n \tu32 key_size;\n \tint err = 0;\n \n-\tif (arg_type == ARG_DONTCARE)\n+\tif (arg_type == ARG_PTR_TO_PROG_AUX) {\n+\t\tcur_aux(env)-\u003earg_prog = regno;\n \t\treturn 0;\n+\t}\n \n-\terr = check_reg_arg(env, regno, SRC_OP);\n-\tif (err)\n-\t\treturn err;\n+\tif (arg_type == ARG_IGNORE)\n+\t\treturn 0;\n+\n+\tif (regno \u003e= 0) {\n+\t\terr = check_reg_arg(env, regno, SRC_OP);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n \n+\t/* Preserve the legacy helper behavior for privileged pointer leaks. */\n \tif (arg_type == ARG_ANYTHING) {\n-\t\tif (is_pointer_value(env, regno)) {\n-\t\t\tverbose(env, \"R%d leaks addr into helper function\\n\",\n-\t\t\t\tregno);\n+\t\tif (__is_pointer_value(env-\u003eallow_ptr_leaks, reg)) {\n+\t\t\tverbose(env, \"%s leaks addr into helper function\\n\",\n+\t\t\t\treg_arg_name(env, argno));\n \t\t\treturn -EACCES;\n \t\t}\n \t\treturn 0;\n \t}\n \n-\tif (type_is_pkt_pointer(type) \u0026\u0026\n-\t    !may_access_direct_pkt_data(env, fn, BPF_READ)) {\n-\t\tverbose(env, \"helper access to the packet is not allowed\\n\");\n-\t\treturn -EACCES;\n-\t}\n-\n-\tif (base_type(arg_type) == ARG_PTR_TO_MAP_VALUE) {\n-\t\terr = resolve_map_arg_type(env, meta, \u0026arg_type);\n-\t\tif (err)\n-\t\t\treturn err;\n-\t}\n+\terr = resolve_func_arg_type(env, reg, arg, meta, \u0026arg_type, \u0026arg_size);\n+\tif (err)\n+\t\treturn err;\n \n \tif (bpf_register_is_null(reg) \u0026\u0026 type_may_be_null(arg_type)) {\n-\t\t/* A NULL register has a SCALAR_VALUE type, so skip\n-\t\t * type checking.\n-\t\t */\n-\t\terr = mark_chain_precision(env, regno);\n+\t\terr = mark_arg_precision(env, argno);\n \t\tif (err)\n \t\t\treturn err;\n-\t\tgoto skip_type_check;\n+\t\treturn 0;\n \t}\n \n-\t/* arg_btf_id and arg_size are in a union. */\n-\tif (base_type(arg_type) == ARG_PTR_TO_BTF_ID ||\n-\t    base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)\n-\t\targ_btf_id = fn-\u003earg_btf_id[arg];\n+\terr = check_func_arg_nullability(env, reg, argno, arg_type, meta, insn_idx);\n+\tif (err)\n+\t\treturn err;\n \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+\terr = check_reg_type(env, reg, argno, arg_type, meta);\n \tif (err)\n \t\treturn err;\n \n@@ -8878,22 +8948,27 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \tif (err)\n \t\treturn err;\n \n-skip_type_check:\n-\tif (arg_type_is_release(arg_type) \u0026\u0026 !arg_type_is_dynptr(arg_type) \u0026\u0026\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+\terr = check_func_arg_release(env, reg, argno, arg_type, meta, insn_idx);\n+\tif (err)\n+\t\treturn err;\n \n \tif (reg_is_referenced(env, reg))\n \t\tupdate_ref_obj(\u0026meta-\u003eref_obj, reg);\n \n \tswitch (base_type(arg_type)) {\n+\tcase ARG_CONST_SCALAR:\n+\t\terr = process_const_arg(env, reg, argno, meta);\n+\t\tif (err \u003c 0) {\n+\t\t\tif (err == -EINVAL)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\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      \"the function requires this scalar argument to be a verifier-known constant, but %s is variable on this path\",\n+\t\t\t\t\t\t      reg_arg_name(env, argno));\n+\t\t\treturn err;\n+\t\t}\n+\t\tbreak;\n+\tcase ARG_SCALAR:\n+\t\tbreak;\n \tcase ARG_CONST_MAP_PTR:\n \t\t/* bpf_map_xxx(map_ptr) call: remember that map_ptr */\n \t\terr = process_map_ptr_arg(env, reg, argno, meta);\n@@ -8915,7 +8990,7 @@ 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, meta,\n \t\t\t\t\t      NULL);\n \t\tif (err)\n \t\t\treturn err;\n@@ -8947,7 +9022,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t * Disable raw mode for bpf_map_peek_elem() on a bloom filter. The helper reads\n \t\t * the value buffer as an input rather than filling it.\n \t\t */\n-\t\tif (meta-\u003efunc_id == BPF_FUNC_map_peek_elem \u0026\u0026\n+\t\tif (is_helper_call(meta, BPF_FUNC_map_peek_elem) \u0026\u0026\n \t\t    meta-\u003emap.ptr-\u003emap_type == BPF_MAP_TYPE_BLOOM_FILTER)\n \t\t\tmeta-\u003earg_raw_mem.regno = 0;\n \n@@ -8955,9 +9030,79 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t\t\t\t      arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ,\n \t\t\t\t\t      false, meta, NULL);\n \t\tbreak;\n+\tcase ARG_PTR_TO_BTF_ID:\n+\tcase ARG_PTR_TO_BTF_ID_SOCK_COMMON:\n+\t{\n+\t\tconst u32 *arg_btf_id = fn-\u003earg_btf_id[arg];\n+\t\tconst struct btf *arg_btf = meta-\u003ebtf ?: btf_vmlinux;\n+\n+\t\tif (!meta-\u003ebtf) {\n+\t\t\tconst struct bpf_reg_types *compatible;\n+\n+\t\t\tif (base_type(reg-\u003etype) != PTR_TO_BTF_ID)\n+\t\t\t\tbreak;\n+\n+\t\t\tif (is_helper_call(meta, BPF_FUNC_kptr_xchg))\n+\t\t\t\treturn map_kptr_match_type(env, meta-\u003ekptr_field, reg, regno) ?\n+\t\t\t\t       -EACCES : 0;\n+\n+\t\t\tif (!arg_btf_id) {\n+\t\t\t\tcompatible = compatible_reg_types[base_type(arg_type)];\n+\t\t\t\tif (!compatible-\u003ebtf_id) {\n+\t\t\t\t\tverifier_bug(env, \"missing arg compatible BTF ID\");\n+\t\t\t\t\treturn -EFAULT;\n+\t\t\t\t}\n+\t\t\t\targ_btf_id = compatible-\u003ebtf_id;\n+\t\t\t}\n+\t\t\tif (arg_btf_id == BPF_PTR_POISON) {\n+\t\t\t\tverbose(env, \"verifier internal error:\");\n+\t\t\t\tverbose(env, \"%s has non-overwritten BPF_PTR_POISON type\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EACCES;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (meta-\u003ebtf \u0026\u0026 (!is_trusted_reg(env, reg) ||\n+\t\t\t\t  bpf_type_has_unsafe_modifiers(reg-\u003etype))) {\n+\t\t\tif (!(arg_type \u0026 MEM_RCU)) {\n+\t\t\t\tconst char *actual_type, *arg_name, *expected_type;\n+\n+\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);\n+\t\t\t\tverbose(env, \"%s must be referenced or trusted\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\targ_name = reg_arg_name(env, argno);\n+\t\t\t\tactual_type = bpf_diag_reg_type_plain(env, reg-\u003etype);\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t\t      \"Pass a pointer acquired from a verifier-tracked source, or call this function only inside the required protection if it accepts RCU pointers.\",\n+\t\t\t\t\t\t      \"the function requires a trusted or resource-owning pointer to %s, but %s is %s\",\n+\t\t\t\t\t\t      expected_type, arg_name, actual_type);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tif (!is_rcu_reg(reg)) {\n+\t\t\t\tconst char *actual_type, *arg_name, *expected_type;\n+\n+\t\t\t\texpected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);\n+\t\t\t\tverbose(env, \"%s must be a rcu pointer\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\targ_name = reg_arg_name(env, argno);\n+\t\t\t\tactual_type = bpf_diag_reg_type_plain(env, reg-\u003etype);\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t\t      \"Use this function with a pointer that is valid in an RCU read lock region.\",\n+\t\t\t\t\t\t      \"the function requires an RCU-protected pointer to %s, but %s is %s\",\n+\t\t\t\t\t\t      expected_type, arg_name, actual_type);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t}\n+\n+\t\terr = process_arg_ptr_to_btf_id(env, reg, argno, arg_type, arg_btf,\n+\t\t\t\t\t\t*arg_btf_id, meta, insn_idx);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\t}\n \tcase ARG_PTR_TO_PERCPU_BTF_ID:\n \t\tif (!reg-\u003ebtf_id) {\n-\t\t\tverbose(env, \"Helper has invalid btf_id in R%d\\n\", regno);\n+\t\t\tverbose(env, \"Helper has invalid btf_id in %s\\n\", reg_arg_name(env, argno));\n \t\t\treturn -EACCES;\n \t\t}\n \t\tmeta-\u003eret_btf = reg-\u003ebtf;\n@@ -8968,11 +9113,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t\tverbose(env, \"can't spin_{lock,unlock} in rbtree cb\\n\");\n \t\t\treturn -EACCES;\n \t\t}\n-\t\tif (meta-\u003efunc_id == BPF_FUNC_spin_lock) {\n+\t\tif (is_helper_call(meta, BPF_FUNC_spin_lock)) {\n \t\t\terr = process_spin_lock(env, reg, argno, PROCESS_SPIN_LOCK);\n \t\t\tif (err)\n \t\t\t\treturn err;\n-\t\t} else if (meta-\u003efunc_id == BPF_FUNC_spin_unlock) {\n+\t\t} else if (is_helper_call(meta, BPF_FUNC_spin_unlock)) {\n \t\t\terr = process_spin_lock(env, reg, argno, 0);\n \t\t\tif (err)\n \t\t\t\treturn err;\n@@ -8986,45 +9131,274 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\tif (err)\n \t\t\treturn err;\n \t\tbreak;\n+\tcase ARG_PTR_TO_CTX:\n+\t\tif (is_bpf_cast_to_kern_ctx_kfunc(meta)) {\n+\t\t\terr = get_kern_ctx_btf_id(\u0026env-\u003elog, resolve_prog_type(env-\u003eprog));\n+\t\t\tif (err \u003c 0)\n+\t\t\t\treturn -EINVAL;\n+\t\t\tmeta-\u003eret_btf_id = err;\n+\t\t}\n+\t\tbreak;\n+\tcase ARG_PTR_TO_ARENA:\n+\t\tbreak;\n+\tcase ARG_PTR_TO_ALLOC_BTF_ID:\n+\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC)) {\n+\t\t\tif (!is_bpf_obj_drop_kfunc(meta-\u003efunc_id)) {\n+\t\t\t\tverbose(env, \"%s expected for bpf_obj_drop()\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t} else if (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {\n+\t\t\tif (!is_bpf_percpu_obj_drop_kfunc(meta-\u003efunc_id)) {\n+\t\t\t\tverbose(env, \"%s expected for bpf_percpu_obj_drop()\\n\",\n+\t\t\t\t\treg_arg_name(env, argno));\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t}\n+\t\tif (!reg_is_referenced(env, reg)) {\n+\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n+\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t      \"Pass the owned object pointer before it is released or transferred.\",\n+\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      reg_arg_name(env, argno));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tif (meta-\u003ebtf == btf_vmlinux) {\n+\t\t\tmeta-\u003earg_btf = reg-\u003ebtf;\n+\t\t\tmeta-\u003earg_btf_id = reg-\u003ebtf_id;\n+\t\t}\n+\t\tbreak;\n \tcase ARG_PTR_TO_FUNC:\n \t\tmeta-\u003esubprogno = reg-\u003esubprogno;\n \t\tbreak;\n \tcase ARG_PTR_TO_MEM:\n+\t{\n+\t\tenum bpf_access_type access_type;\n+\t\tbool known_memory;\n+\n \t\t/* The access to this pointer is only checked when we hit the\n \t\t * next is_mem_size argument below.\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, NULL);\n-\t\t\tif (err)\n-\t\t\t\treturn err;\n-\t\t\tif (arg_type \u0026 MEM_ALIGNED)\n-\t\t\t\terr = check_ptr_alignment(env, reg, 0, fn-\u003earg_size[arg], true);\n+\t\tif (!(arg_type \u0026 MEM_FIXED_SIZE))\n+\t\t\tbreak;\n+\n+\t\taccess_type = arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ;\n+\t\tif (meta-\u003ebtf)\n+\t\t\taccess_type = BPF_READ | BPF_WRITE;\n+\n+\t\terr = check_mem_reg(env, reg, argno, arg_size, access_type, meta, \u0026known_memory);\n+\t\tif (err \u003c 0) {\n+\t\t\tif (known_memory)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t\"Pass memory with at least the required number of accessible bytes and suitable read or write access.\",\n+\t\t\t\t\t\"the function expects %u bytes of memory, but the verifier cannot prove that %s provides a range of that size with the required read or write access\",\n+\t\t\t\t\targ_size,\n+\t\t\t\t\tbpf_diag_reg_type_plain(env, reg-\u003etype));\n+\t\t\telse\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\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\"the function expects %u bytes of memory, but it is %s and not verifier-known memory\",\n+\t\t\t\t\targ_size,\n+\t\t\t\t\tbpf_diag_reg_type_plain(env, reg-\u003etype));\n+\t\t\treturn err;\n \t\t}\n+\t\tif (arg_type \u0026 MEM_ALIGNED)\n+\t\t\terr = check_ptr_alignment(env, reg, 0, arg_size, true);\n \t\tbreak;\n+\t}\n+\tcase ARG_CONST_MEM_SIZE:\n+\t\terr = process_const_arg(env, reg, argno, meta);\n+\t\tif (err \u003c 0) {\n+\t\t\tif (err == -EINVAL)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\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      \"the function requires this memory size to be a verifier-known constant, but %s is variable on this path\",\n+\t\t\t\t\t\t      reg_arg_name(env, argno));\n+\t\t\treturn err;\n+\t\t}\n+\t\tfallthrough;\n \tcase ARG_MEM_SIZE:\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, 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, NULL);\n-\t\tbreak;\n-\tcase ARG_PTR_TO_DYNPTR:\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{\n+\t\tstruct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, arg - 1);\n+\t\targno_t buff_argno = argno_from_arg(arg);\n+\t\tenum bpf_mem_size_failure failure;\n+\t\tconst char *buff_arg, *size_arg;\n+\t\tbool zero_size_allowed;\n+\t\tu32 access_type;\n+\n+\t\tif (meta-\u003ebtf \u0026\u0026 bpf_register_is_null(buff_reg))\n+\t\t\tbreak;\n+\n+\t\taccess_type = fn-\u003earg_type[arg - 1] \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ;\n+\t\tif (meta-\u003ebtf)\n+\t\t\taccess_type = BPF_READ | BPF_WRITE;\n+\n+\t\tzero_size_allowed = meta-\u003ebtf || base_type(arg_type) == ARG_MEM_SIZE_OR_ZERO;\n+\n+\t\terr = check_mem_size_reg(env, buff_reg, reg, buff_argno, argno,\n+\t\t\t\t\t access_type, zero_size_allowed, meta, \u0026failure);\n+\t\tif (!err)\n+\t\t\tbreak;\n+\n+\t\tbuff_arg = bpf_diag_arg_name(env, buff_argno);\n+\t\tsize_arg = bpf_diag_arg_name(env, argno);\n+\t\tverbose(env, \"%s and \", reg_arg_name(env, buff_argno));\n+\t\tverbose(env, \"%s memory, len pair leads to invalid memory access\\n\",\n+\t\t\treg_arg_name(env, argno));\n+\t\tif (failure == BPF_MEM_SIZE_FAIL_MEMORY) {\n+\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, buff_argno, meta-\u003efunc_name,\n+\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      \"it is the memory pointer in a memory/length pair with %s, but %s does not provide a verifier-accessible range of the requested length\",\n+\t\t\t\t\t      size_arg, buff_arg);\n+\t\t} else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {\n+\t\t\tif (reg_smin(reg) \u003c 0)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\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\"the memory size in %s may be negative because its signed minimum is %lld\",\n+\t\t\t\t\tsize_arg, reg_smin(reg));\n+\t\t\telse if (!zero_size_allowed \u0026\u0026 reg_umin(reg) == 0)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t\"Ensure the memory size is non-zero before this call.\",\n+\t\t\t\t\t\"the memory size in %s may be zero, but the function requires a non-zero size\",\n+\t\t\t\t\tsize_arg);\n+\t\t\telse\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\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\"the memory size in %s may reach %llu bytes, but variable memory accesses must stay below %u bytes\",\n+\t\t\t\t\tsize_arg, reg_umax(reg), BPF_MAX_VAR_SIZ);\n+\t\t}\n+\t\tbreak;\n+\t}\n+\tcase ARG_PTR_TO_DYNPTR: {\n+\t\tif (is_bpf_dynptr_clone_kfunc(meta) \u0026\u0026\n+\t\t    (arg_type \u0026 MEM_UNINIT)) {\n+\t\t\tenum bpf_dynptr_type parent_type = meta-\u003edynptr.type;\n+\n+\t\t\tif (parent_type == BPF_DYNPTR_TYPE_INVALID) {\n+\t\t\t\tverifier_bug(env, \"no dynptr type for parent of clone\");\n+\t\t\t\treturn -EFAULT;\n+\t\t\t}\n+\n+\t\t\targ_type |= (unsigned int)get_dynptr_type_flag(parent_type);\n+\t\t}\n+\n+\t\terr = process_dynptr_func(env, reg, argno, insn_idx, arg_type, meta);\n \t\tif (err)\n \t\t\treturn err;\n \t\tbreak;\n+\t}\n+\tcase ARG_PTR_TO_ITER:\n+\t\tif (is_bpf_iter_css_task_new_kfunc(meta) \u0026\u0026\n+\t\t    !check_css_task_iter_allowlist(env)) {\n+\t\t\tverbose(env, \"css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\\n\");\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\terr = process_iter_arg(env, reg, argno, insn_idx, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_LIST_HEAD:\n+\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC) \u0026\u0026\n+\t\t    !reg_is_referenced(env, reg)) {\n+\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\terr = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_RB_ROOT:\n+\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC) \u0026\u0026\n+\t\t    !reg_is_referenced(env, reg)) {\n+\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\terr = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_LIST_NODE:\n+\t\tif (!(is_kfunc_arg_nonown_allowed(meta-\u003ebtf, btf_arg) \u0026\u0026\n+\t\t      type_is_non_owning_ref(reg-\u003etype) \u0026\u0026 !reg_is_referenced(env, reg))) {\n+\t\t\tif (reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\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\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\treturn -EINVAL;\n+\t\t\t}\n+\t\t}\n+\t\terr = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_RB_NODE:\n+\t\tif (is_bpf_rbtree_add_kfunc(meta-\u003efunc_id)) {\n+\t\t\tif (reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\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\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\treturn -EINVAL;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tif (!type_is_non_owning_ref(reg-\u003etype) \u0026\u0026\n+\t\t\t    !reg_is_referenced(env, reg)) {\n+\t\t\t\tverbose(env, \"%s can only take non-owning or refcounted bpf_rb_node pointer\\n\",\n+\t\t\t\t\tmeta-\u003efunc_name);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tif (in_rbtree_lock_required_cb(env)) {\n+\t\t\t\tverbose(env, \"%s not allowed in rbtree cb\\n\", meta-\u003efunc_name);\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t}\n+\t\terr = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n \tcase ARG_CONST_ALLOC_SIZE_OR_ZERO:\n+\t\tif (meta-\u003ebtf \u0026\u0026 is_kfunc_arg_scalar_with_name(meta-\u003ebtf, btf_arg,\n+\t\t\t\t\t\t\t       \"rdonly_buf_size\"))\n+\t\t\tmeta-\u003er0_rdonly = true;\n \t\terr = process_const_alloc_mem_size(env, reg, argno, \u0026meta-\u003eret_mem);\n-\t\tif (err)\n+\t\tif (err \u003c 0) {\n+\t\t\tif (meta-\u003ebtf \u0026\u0026 err == -EINVAL)\n+\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n+\t\t\t\t\t\t      \"Pass a verifier-known constant size for this function's buffer argument.\",\n+\t\t\t\t\t\t      \"the function uses this argument as a return-buffer size, but %s is invalid or variable on this path\",\n+\t\t\t\t\t\t      reg_arg_name(env, argno));\n \t\t\treturn err;\n+\t\t}\n \t\tbreak;\n+\tcase ARG_PTR_TO_REFCOUNTED_KPTR:\n+\t{\n+\t\tstruct btf_record *rec;\n+\n+\t\tif (!type_is_non_owning_ref(reg-\u003etype) \u0026\u0026 reg_is_referenced(env, reg))\n+\t\t\tmeta-\u003earg_owning_ref = true;\n+\n+\t\trec = reg_btf_record(reg);\n+\t\tif (!rec) {\n+\t\t\tverifier_bug(env, \"Couldn't find btf_record\");\n+\t\t\treturn -EFAULT;\n+\t\t}\n+\n+\t\tif (rec-\u003erefcount_off \u003c 0) {\n+\t\t\tverbose(env, \"%s doesn't point to a type with bpf_refcount field\\n\",\n+\t\t\t\treg_arg_name(env, argno));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\tmeta-\u003earg_btf = reg-\u003ebtf;\n+\t\tmeta-\u003earg_btf_id = reg-\u003ebtf_id;\n+\t\tbreak;\n+\t}\n \tcase ARG_PTR_TO_CONST_STR:\n \t{\n \t\terr = check_arg_const_str(env, reg, argno);\n@@ -9032,6 +9406,38 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \t\t\treturn err;\n \t\tbreak;\n \t}\n+\tcase ARG_PTR_TO_WORKQUEUE:\n+\t\terr = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, \u0026meta-\u003emap);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_TASK_WORK:\n+\t\terr = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, \u0026meta-\u003emap);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_IRQ_FLAG:\n+\t\terr = process_irq_flag(env, reg, argno, meta);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\tcase ARG_PTR_TO_RES_SPIN_LOCK:\n+\t{\n+\t\tint flags;\n+\n+\t\tif (in_rbtree_lock_required_cb(env)) {\n+\t\t\tverbose(env, \"can't res_spin_{lock,unlock} in rbtree cb\\n\");\n+\t\t\treturn -EACCES;\n+\t\t}\n+\n+\t\tflags = get_bpf_res_spin_lock_kfunc_flags(meta);\n+\t\tif (!flags)\n+\t\t\treturn -EFAULT;\n+\t\terr = process_spin_lock(env, reg, argno, flags);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tbreak;\n+\t}\n \tcase ARG_KPTR_XCHG_DEST:\n \t\terr = process_kptr_func(env, regno, meta);\n \t\tif (err)\n@@ -9042,6 +9448,37 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n \treturn err;\n }\n \n+static int check_func_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n+\t\t\t   int insn_idx)\n+{\n+\tstruct bpf_func_state *caller = cur_func(env);\n+\tconst struct btf_param *args = NULL;\n+\tu32 arg, nargs = MAX_BPF_FUNC_REG_ARGS;\n+\tint err;\n+\n+\tif (meta-\u003ebtf) {\n+\t\targs = btf_params(meta-\u003efunc_proto);\n+\t\tnargs = btf_type_vlen(meta-\u003efunc_proto);\n+\t}\n+\n+\tif (nargs \u003e MAX_BPF_FUNC_REG_ARGS) {\n+\t\terr = check_outgoing_stack_args(env, caller, nargs, meta-\u003efunc_name,\n+\t\t\t\t\t\tmeta-\u003ebtf, args);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\tfor (arg = 0; arg \u003c nargs; arg++) {\n+\t\tif (meta-\u003efn-\u003earg_type[arg] == ARG_UNUSED)\n+\t\t\tbreak;\n+\t\terr = check_func_arg(env, arg, meta, insn_idx);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id)\n {\n \tenum bpf_attach_type eatype = env-\u003eprog-\u003eexpected_attach_type;\n@@ -9339,7 +9776,7 @@ static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_a\n \tint i;\n \n \tfor (i = 0; i \u003c ARRAY_SIZE(fn-\u003earg_type); i++) {\n-\t\tif (fn-\u003earg_type[i] == ARG_DONTCARE)\n+\t\tif (fn-\u003earg_type[i] == ARG_UNUSED)\n \t\t\tbreak;\n \t\tif (!arg_type_is_raw_mem(fn-\u003earg_type[i]))\n \t\t\tcontinue;\n@@ -9389,7 +9826,7 @@ static bool check_btf_id_ok(const struct bpf_func_proto *fn)\n \tint i;\n \n \tfor (i = 0; i \u003c ARRAY_SIZE(fn-\u003earg_type); i++) {\n-\t\tif (fn-\u003earg_type[i] == ARG_DONTCARE)\n+\t\tif (fn-\u003earg_type[i] == ARG_UNUSED)\n \t\t\tbreak;\n \t\tif (base_type(fn-\u003earg_type[i]) == ARG_PTR_TO_BTF_ID)\n \t\t\treturn !!fn-\u003earg_btf_id[i];\n@@ -9412,7 +9849,7 @@ static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)\n \tfor (i = 0; i \u003c ARRAY_SIZE(fn-\u003earg_type); i++) {\n \t\tenum bpf_arg_type arg_type = fn-\u003earg_type[i];\n \n-\t\tif (arg_type == ARG_DONTCARE)\n+\t\tif (arg_type == ARG_UNUSED)\n \t\t\tbreak;\n \t\tif (base_type(arg_type) != ARG_PTR_TO_MEM)\n \t\t\tcontinue;\n@@ -9430,7 +9867,7 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_\n \tfor (i = 0; i \u003c ARRAY_SIZE(fn-\u003earg_type); i++) {\n \t\tenum bpf_arg_type arg_type = fn-\u003earg_type[i];\n \n-\t\tif (arg_type == ARG_DONTCARE)\n+\t\tif (arg_type == ARG_UNUSED)\n \t\t\tbreak;\n \t\tif (arg_type_is_release(arg_type)) {\n \t\t\tif (meta-\u003erelease_regno)\n@@ -9442,9 +9879,42 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_\n \treturn true;\n }\n \n-static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)\n+static bool check_arg_prog_aux(struct bpf_verifier_env *env,\n+\t\t\t       const struct bpf_func_proto *proto)\n {\n-\treturn check_raw_mode_ok(fn, meta) \u0026\u0026\n+\tbool seen = false;\n+\targno_t argno;\n+\tu32 i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(proto-\u003earg_type); i++) {\n+\t\tif (proto-\u003earg_type[i] == ARG_UNUSED)\n+\t\t\tbreak;\n+\t\tif (proto-\u003earg_type[i] != ARG_PTR_TO_PROG_AUX)\n+\t\t\tcontinue;\n+\n+\t\tif (seen) {\n+\t\t\tverifier_bug(env, \"Only 1 prog-\u003eaux argument supported\");\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\targno = argno_from_arg(i + 1);\n+\t\tif (reg_from_argno(argno) \u003c 0) {\n+\t\t\tverbose(env, \"%s prog-\u003eaux cannot be a stack argument\\n\",\n+\t\t\t\treg_arg_name(env, argno));\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\tseen = true;\n+\t}\n+\n+\treturn true;\n+}\n+\n+static int check_func_proto(struct bpf_verifier_env *env, const struct bpf_func_proto *fn,\n+\t\t\t    struct bpf_call_arg_meta *meta)\n+{\n+\treturn check_arg_prog_aux(env, fn) \u0026\u0026\n+\t       check_raw_mode_ok(fn, meta) \u0026\u0026\n \t       check_arg_pair_ok(fn) \u0026\u0026\n \t       check_mem_arg_rw_flag_ok(fn) \u0026\u0026\n \t       check_proto_release_reg(fn, meta) \u0026\u0026\n@@ -9664,7 +10134,8 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 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\tif (!type_may_be_null(reg-\u003etype))\n+\t\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@@ -9763,12 +10234,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \tstruct bpf_subprog_info *sub = subprog_info(env, 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+\tstruct bpf_call_arg_meta meta;\n \tu32 i;\n \tint ret, err;\n \n+\t/* Leave btf and func_id zero: this is neither a helper nor a kfunc. */\n+\tmemset(\u0026meta, 0, sizeof(meta));\n+\tmeta.func_name = bpf_subprog_name(env, subprog);\n+\n \tret = btf_prepare_func_args(env, subprog);\n \tif (ret) {\n \t\tif (bpf_in_stack_arg_cnt(sub) \u003e 0) {\n@@ -9797,7 +10272,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\tstruct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);\n \t\tstruct bpf_subprog_arg_info *arg = \u0026sub-\u003eargs[i];\n \n-\t\tif (arg-\u003earg_type == ARG_ANYTHING) {\n+\t\tif (arg-\u003earg_type == ARG_SCALAR) {\n \t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n \t\t\t\tbpf_log(log, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n \t\t\t\treturn -EINVAL;\n@@ -9821,7 +10296,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\t\treturn -EINVAL;\n \t\t\t}\n \t\t} else if (base_type(arg-\u003earg_type) == ARG_PTR_TO_MEM) {\n-\t\t\tret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);\n+\t\t\tret = check_func_arg_reg_off(env, reg, argno, ARG_PTR_TO_MEM);\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@@ -9852,12 +10327,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\t\treturn ret;\n \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\t\t\t\t  arg-\u003earg_type, \u0026meta);\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-\t\t\tstruct bpf_call_arg_meta meta;\n \t\t\tint err;\n \n \t\t\tif (bpf_register_is_null(reg) \u0026\u0026 type_may_be_null(arg-\u003earg_type)) {\n@@ -9867,10 +10340,12 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n \t\t\t\tcontinue;\n \t\t\t}\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\t\t\t     bpf_subprog_name(env, subprog));\n+\t\t\terr = check_reg_type(env, reg, argno, arg-\u003earg_type, \u0026meta);\n \t\t\terr = err ?: check_func_arg_reg_off(env, reg, argno, arg-\u003earg_type);\n+\t\t\tif (!err \u0026\u0026 base_type(reg-\u003etype) == PTR_TO_BTF_ID)\n+\t\t\t\terr = process_arg_ptr_to_btf_id(env, reg, argno, arg-\u003earg_type,\n+\t\t\t\t\t\t\t\tbtf_vmlinux, arg-\u003ebtf_id,\n+\t\t\t\t\t\t\t\t\u0026meta, env-\u003einsn_idx);\n \t\t\tif (err)\n \t\t\t\treturn err;\n \t\t} else {\n@@ -10987,7 +11462,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \n \tmemset(\u0026meta, 0, sizeof(meta));\n \n-\terr = check_func_proto(fn, \u0026meta);\n+\terr = check_func_proto(env, fn, \u0026meta);\n \tif (err) {\n \t\tverifier_bug(env, \"incorrect func proto %s#%d\", func_id_name(func_id), func_id);\n \t\treturn err;\n@@ -11008,13 +11483,11 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn\n \t\tenv-\u003einsn_aux_data[insn_idx].non_sleepable = true;\n \n \tmeta.func_id = func_id;\n+\tmeta.func_name = func_id_name(func_id);\n \tmeta.fn = fn;\n-\t/* check args */\n-\tfor (i = 0; i \u003c MAX_BPF_FUNC_REG_ARGS; i++) {\n-\t\terr = check_func_arg(env, i, \u0026meta, insn_idx);\n-\t\tif (err)\n-\t\t\treturn err;\n-\t}\n+\terr = check_func_args(env, \u0026meta, insn_idx);\n+\tif (err)\n+\t\treturn err;\n \n \terr = record_func_map(env, \u0026meta, func_id, insn_idx);\n \tif (err)\n@@ -11853,6 +12326,50 @@ static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,\n \treturn btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR);\n }\n \n+static int resolve_func_arg_type(struct bpf_verifier_env *env,\n+\t\t\t\t struct bpf_reg_state *reg, u32 arg,\n+\t\t\t\t struct bpf_call_arg_meta *meta,\n+\t\t\t\t enum bpf_arg_type *arg_type, u32 *arg_size)\n+{\n+\targno_t argno = argno_from_arg(arg + 1);\n+\tconst struct btf_param *args;\n+\tconst struct btf_type *ref_t, *resolve_ret;\n+\tconst struct btf *btf;\n+\tconst char *ref_tname;\n+\tu32 ref_id;\n+\n+\tif (base_type(*arg_type) == ARG_PTR_TO_MAP_VALUE)\n+\t\treturn resolve_map_arg_type(env, meta, arg_type);\n+\n+\tif (base_type(*arg_type) != ARG_PTR_TO_BTF_ID)\n+\t\treturn 0;\n+\n+\tif (!meta-\u003ebtf || arg_type_is_release(*arg_type) ||\n+\t    base_type(reg-\u003etype) == PTR_TO_BTF_ID ||\n+\t    reg2btf_ids[base_type(reg-\u003etype)])\n+\t\treturn 0;\n+\n+\targs = btf_params(meta-\u003efunc_proto);\n+\tref_id = *meta-\u003efn-\u003earg_btf_id[arg];\n+\tbtf = is_kfunc_arg_map(meta-\u003ebtf, \u0026args[arg]) ? btf_vmlinux : meta-\u003ebtf;\n+\tref_t = btf_type_skip_modifiers(btf, ref_id, \u0026ref_id);\n+\tref_tname = btf_name_by_offset(btf, ref_t-\u003ename_off);\n+\n+\tif (!btf_type_is_scalar_struct(env, btf, ref_t))\n+\t\treturn 0;\n+\n+\tresolve_ret = btf_resolve_size(btf, ref_t, arg_size);\n+\tif (IS_ERR(resolve_ret)) {\n+\t\tverbose(env, \"%s reference type('%s %s') size cannot be determined: %ld\\n\",\n+\t\t\treg_arg_name(env, argno), btf_type_str(ref_t), ref_tname,\n+\t\t\tPTR_ERR(resolve_ret));\n+\t\treturn -EINVAL;\n+\t}\n+\t*arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | (*arg_type \u0026 PTR_MAYBE_NULL);\n+\n+\treturn 0;\n+}\n+\n static void btf_member_path_str(const struct btf *btf, const struct btf_member_path *path,\n \t\t\t\tchar *buf, size_t buf_sz)\n {\n@@ -11872,34 +12389,6 @@ static void btf_member_path_str(const struct btf *btf, const struct btf_member_p\n \t}\n }\n \n-enum kfunc_ptr_arg_type {\n-\tKF_ARG_CONST_MEM_SIZE,\n-\tKF_ARG_MEM_SIZE,\n-\tKF_ARG_CONST,\n-\tKF_ARG_CONST_ALLOC_SIZE_OR_ZERO,\n-\tKF_ARG_ANYTHING,\n-\tKF_ARG_PTR_TO_CTX,\n-\tKF_ARG_PTR_TO_ALLOC_BTF_ID,    /* Allocated object */\n-\tKF_ARG_PTR_TO_REFCOUNTED_KPTR, /* Refcounted local kptr */\n-\tKF_ARG_PTR_TO_DYNPTR,\n-\tKF_ARG_PTR_TO_ITER,\n-\tKF_ARG_PTR_TO_LIST_HEAD,\n-\tKF_ARG_PTR_TO_LIST_NODE,\n-\tKF_ARG_PTR_TO_BTF_ID,\t       /* Also covers reg2btf_ids conversions */\n-\tKF_ARG_PTR_TO_MEM,\n-\tKF_ARG_PTR_TO_CALLBACK,\n-\tKF_ARG_PTR_TO_RB_ROOT,\n-\tKF_ARG_PTR_TO_RB_NODE,\n-\tKF_ARG_PTR_TO_CONST_STR,\n-\tKF_ARG_CONST_MAP_PTR,\n-\tKF_ARG_PTR_TO_TIMER,\n-\tKF_ARG_PTR_TO_WORKQUEUE,\n-\tKF_ARG_PTR_TO_IRQ_FLAG,\n-\tKF_ARG_PTR_TO_RES_SPIN_LOCK,\n-\tKF_ARG_PTR_TO_TASK_WORK,\n-\tKF_ARG_PTR_TO_ARENA,\n-};\n-\n enum special_kfunc_type {\n \tKF_bpf_obj_new_impl,\n \tKF_bpf_obj_new,\n@@ -11967,7 +12456,10 @@ enum special_kfunc_type {\n \tKF_bpf_task_work_schedule_resume,\n \tKF_bpf_arena_alloc_pages,\n \tKF_bpf_arena_free_pages,\n+\tKF_bpf_arena_reserve_pages,\n \tKF_bpf_session_is_return,\n+\tKF_bpf_stream_vprintk,\n+\tKF_bpf_stream_print_stack,\n };\n \n BTF_ID_LIST(special_kfunc_list)\n@@ -12057,11 +12549,29 @@ BTF_ID(func, bpf_task_work_schedule_signal)\n BTF_ID(func, bpf_task_work_schedule_resume)\n BTF_ID(func, bpf_arena_alloc_pages)\n BTF_ID(func, bpf_arena_free_pages)\n+BTF_ID(func, bpf_arena_reserve_pages)\n #ifdef CONFIG_BPF_EVENTS\n BTF_ID(func, bpf_session_is_return)\n #else\n BTF_ID_UNUSED\n #endif\n+BTF_ID(func, bpf_stream_vprintk)\n+BTF_ID(func, bpf_stream_print_stack)\n+\n+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta)\n+{\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]);\n+}\n+\n+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta)\n+{\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_clone]);\n+}\n+\n+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta)\n+{\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_iter_css_task_new]);\n+}\n \n static bool is_bpf_obj_new_kfunc(u32 func_id)\n {\n@@ -12124,52 +12634,63 @@ static bool is_kfunc_ret_null(struct bpf_call_arg_meta *meta)\n \n static bool is_kfunc_bpf_rcu_read_lock(struct bpf_call_arg_meta *meta)\n {\n-\treturn meta-\u003efunc_id == special_kfunc_list[KF_bpf_rcu_read_lock];\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_lock]);\n }\n \n static bool is_kfunc_bpf_rcu_read_unlock(struct bpf_call_arg_meta *meta)\n {\n-\treturn meta-\u003efunc_id == special_kfunc_list[KF_bpf_rcu_read_unlock];\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_unlock]);\n }\n \n static bool is_kfunc_bpf_preempt_disable(struct bpf_call_arg_meta *meta)\n {\n-\treturn meta-\u003efunc_id == special_kfunc_list[KF_bpf_preempt_disable];\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_disable]);\n }\n \n static bool is_kfunc_bpf_preempt_enable(struct bpf_call_arg_meta *meta)\n {\n-\treturn meta-\u003efunc_id == special_kfunc_list[KF_bpf_preempt_enable];\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_enable]);\n }\n \n bool bpf_is_kfunc_pkt_changing(struct bpf_call_arg_meta *meta)\n {\n-\treturn meta-\u003efunc_id == special_kfunc_list[KF_bpf_xdp_pull_data];\n+\treturn is_kfunc_call(meta, special_kfunc_list[KF_bpf_xdp_pull_data]);\n }\n \n static int\n get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n-\t\t   const struct btf_param *args, int arg, int nargs)\n+\t\t   const struct btf_param *args, int arg, int nargs,\n+\t\t   struct bpf_func_proto *proto)\n {\n-\tconst struct btf_type *t, *ref_t = NULL;\n+\tconst struct btf_type *t, *ref_t = NULL, *resolve_ret;\n+\tconst u32 *ref_id_ptr = NULL;\n \targno_t argno = argno_from_arg(arg + 1);\n \tconst char *ref_tname = NULL;\n+\tu32 ref_id, type_size;\n \tint arg_type;\n \n+\tproto-\u003earg_btf_id[arg] = NULL;\n+\n+\tif (is_kfunc_arg_prog_aux(meta-\u003ebtf, \u0026args[arg]))\n+\t\treturn ARG_PTR_TO_PROG_AUX;\n+\n+\tif (is_kfunc_arg_ignore(meta-\u003ebtf, \u0026args[arg]) || is_kfunc_arg_implicit(meta, arg))\n+\t\treturn ARG_IGNORE;\n+\n \tt = btf_type_skip_modifiers(meta-\u003ebtf, args[arg].type, NULL);\n \n \t/* Scalar arguments are classified from their BTF suffix/name alone. */\n \tif (btf_type_is_scalar(t)) {\n \t\tif (is_kfunc_arg_constant(meta-\u003ebtf, \u0026args[arg]))\n-\t\t\treturn KF_ARG_CONST;\n+\t\t\treturn ARG_CONST_SCALAR;\n \t\tif (is_kfunc_arg_const_mem_size(meta-\u003ebtf, \u0026args[arg]))\n-\t\t\treturn KF_ARG_CONST_MEM_SIZE;\n+\t\t\treturn ARG_CONST_MEM_SIZE;\n \t\tif (is_kfunc_arg_mem_size(meta-\u003ebtf, \u0026args[arg]))\n-\t\t\treturn KF_ARG_MEM_SIZE;\n+\t\t\treturn ARG_MEM_SIZE;\n \t\tif (is_kfunc_arg_scalar_with_name(meta-\u003ebtf, \u0026args[arg], \"rdonly_buf_size\") ||\n \t\t    is_kfunc_arg_scalar_with_name(meta-\u003ebtf, \u0026args[arg], \"rdwr_buf_size\"))\n-\t\t\treturn KF_ARG_CONST_ALLOC_SIZE_OR_ZERO;\n-\t\treturn KF_ARG_ANYTHING;\n+\t\t\treturn ARG_CONST_ALLOC_SIZE_OR_ZERO;\n+\t\treturn ARG_SCALAR;\n \t}\n \n \tif (!btf_type_is_ptr(t)) {\n@@ -12177,54 +12698,69 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t\treg_arg_name(env, argno), btf_type_str(t));\n \t\treturn -EINVAL;\n \t}\n-\tref_t = btf_type_skip_modifiers(meta-\u003ebtf, t-\u003etype, NULL);\n+\t/* Keep a pointer to the BTF field containing the resolved referent ID. */\n+\tref_id_ptr = \u0026t-\u003etype;\n+\tref_t = btf_type_skip_modifiers(meta-\u003ebtf, *ref_id_ptr, \u0026ref_id);\n+\twhile (*ref_id_ptr != ref_id)\n+\t\tref_id_ptr = \u0026btf_type_by_id(meta-\u003ebtf, *ref_id_ptr)-\u003etype;\n \tref_tname = btf_name_by_offset(meta-\u003ebtf, ref_t-\u003ename_off);\n \n \t/* In this function, we verify the kfunc's BTF as per the argument type,\n \t * leaving the rest of the verification with respect to the register\n \t * type to our caller. When a set of conditions hold in the BTF type of\n-\t * arguments, we resolve it to a known kfunc_ptr_arg_type.\n+\t * arguments, we resolve it to a known bpf_arg_type.\n \t */\n-\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||\n-\t    meta-\u003efunc_id == special_kfunc_list[KF_bpf_session_is_return] ||\n-\t    meta-\u003efunc_id == special_kfunc_list[KF_bpf_session_cookie])\n-\t\targ_type = KF_ARG_PTR_TO_CTX;\n+\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]) ||\n+\t    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_is_return]) ||\n+\t    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_cookie]))\n+\t\targ_type = ARG_PTR_TO_CTX;\n \telse if (btf_is_prog_ctx_type(\u0026env-\u003elog, meta-\u003ebtf, t, resolve_prog_type(env-\u003eprog), arg))\n-\t\targ_type = KF_ARG_PTR_TO_CTX;\n+\t\targ_type = ARG_PTR_TO_CTX;\n \telse if (is_kfunc_arg_alloc_obj(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_ALLOC_BTF_ID;\n+\t\targ_type = ARG_PTR_TO_ALLOC_BTF_ID;\n \telse if (is_kfunc_arg_refcounted_kptr(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_REFCOUNTED_KPTR;\n-\telse if (is_kfunc_arg_dynptr(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_DYNPTR;\n-\telse if (is_kfunc_arg_iter(meta, arg, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_ITER;\n+\t\targ_type = ARG_PTR_TO_REFCOUNTED_KPTR;\n+\telse if (is_kfunc_arg_dynptr(meta-\u003ebtf, \u0026args[arg])) {\n+\t\targ_type = ARG_PTR_TO_DYNPTR;\n+\n+\t\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb]))\n+\t\t\targ_type |= DYNPTR_TYPE_SKB;\n+\t\telse if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_xdp]))\n+\t\t\targ_type |= DYNPTR_TYPE_XDP;\n+\t\telse if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb_meta]))\n+\t\t\targ_type |= DYNPTR_TYPE_SKB_META;\n+\t\telse if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_file]) ||\n+\t\t\t is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_file_discard]))\n+\t\t\t/* OBJ_RELEASE for the latter comes from KF_RELEASE below */\n+\t\t\targ_type |= DYNPTR_TYPE_FILE;\n+\t} else if (is_kfunc_arg_iter(meta, arg, \u0026args[arg]))\n+\t\targ_type = ARG_PTR_TO_ITER;\n \telse if (is_kfunc_arg_list_head(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_LIST_HEAD;\n+\t\targ_type = ARG_PTR_TO_LIST_HEAD;\n \telse if (is_kfunc_arg_list_node(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_LIST_NODE;\n+\t\targ_type = ARG_PTR_TO_LIST_NODE;\n \telse if (is_kfunc_arg_rbtree_root(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_RB_ROOT;\n+\t\targ_type = ARG_PTR_TO_RB_ROOT;\n \telse if (is_kfunc_arg_rbtree_node(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_RB_NODE;\n+\t\targ_type = ARG_PTR_TO_RB_NODE;\n \telse if (is_kfunc_arg_const_str(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_CONST_STR;\n+\t\targ_type = ARG_PTR_TO_CONST_STR;\n \telse if (is_kfunc_arg_const_map(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_CONST_MAP_PTR;\n+\t\targ_type = ARG_CONST_MAP_PTR;\n \telse if (is_kfunc_arg_map(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_BTF_ID;\n+\t\targ_type = ARG_PTR_TO_BTF_ID;\n \telse if (is_kfunc_arg_wq(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_WORKQUEUE;\n+\t\targ_type = ARG_PTR_TO_WORKQUEUE;\n \telse if (is_kfunc_arg_timer(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_TIMER;\n+\t\targ_type = ARG_PTR_TO_TIMER;\n \telse if (is_kfunc_arg_task_work(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_TASK_WORK;\n+\t\targ_type = ARG_PTR_TO_TASK_WORK;\n \telse if (is_kfunc_arg_irq_flag(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_IRQ_FLAG;\n+\t\targ_type = ARG_PTR_TO_IRQ_FLAG;\n \telse if (is_kfunc_arg_res_spin_lock(meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;\n+\t\targ_type = ARG_PTR_TO_RES_SPIN_LOCK;\n \telse if (is_kfunc_arg_callback(env, meta-\u003ebtf, \u0026args[arg]))\n-\t\targ_type = KF_ARG_PTR_TO_CALLBACK;\n+\t\targ_type = ARG_PTR_TO_FUNC;\n \telse if (is_kfunc_arg_arena(meta-\u003ebtf, \u0026args[arg])) {\n \t\tif (!bpf_jit_supports_arena_args()) {\n \t\t\tverbose(env, \"JIT does not support kfunc %s() with arena pointer arguments\\n\",\n@@ -12247,7 +12783,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t * whether the JIT rebases it to the arena base or preserves NULL.\n \t\t * The common nullable path below records that verifier property.\n \t\t */\n-\t\targ_type = KF_ARG_PTR_TO_ARENA;\n+\t\targ_type = ARG_PTR_TO_ARENA;\n \t} else if (arg + 1 \u003c nargs \u0026\u0026\n \t\t (is_kfunc_arg_mem_size(meta-\u003ebtf, \u0026args[arg + 1]) ||\n \t\t  is_kfunc_arg_const_mem_size(meta-\u003ebtf, \u0026args[arg + 1]))) {\n@@ -12257,10 +12793,10 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t\t\treg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);\n \t\t\treturn -EINVAL;\n \t\t}\n-\t\targ_type = KF_ARG_PTR_TO_MEM;\n+\t\targ_type = ARG_PTR_TO_MEM;\n \t} else if (btf_type_is_struct(ref_t))\n-\t\t/* A pointer to a struct without a size argument is classified as KF_ARG_PTR_TO_BTF_ID */\n-\t\targ_type = KF_ARG_PTR_TO_BTF_ID;\n+\t\t/* A pointer to a struct without a size argument is classified as ARG_PTR_TO_BTF_ID */\n+\t\targ_type = ARG_PTR_TO_BTF_ID;\n \telse {\n \t\t/*\n \t\t * Otherwise this is a fixed-size memory buffer supported by\n@@ -12273,19 +12809,52 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t\t\treg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);\n \t\t\treturn -EINVAL;\n \t\t}\n-\t\targ_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\n+\t\tresolve_ret = btf_resolve_size(meta-\u003ebtf, ref_t, \u0026type_size);\n+\t\tif (IS_ERR(resolve_ret)) {\n+\t\t\tverbose(env,\n+\t\t\t\t\"%s reference type('%s %s') size cannot be determined: %ld\\n\",\n+\t\t\t\treg_arg_name(env, argno), btf_type_str(ref_t),\n+\t\t\t\tref_tname, PTR_ERR(resolve_ret));\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t\tproto-\u003earg_size[arg] = type_size;\n+\t\targ_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\n \t}\n \n+\tif (is_kfunc_arg_uninit(meta-\u003ebtf, \u0026args[arg]))\n+\t\targ_type |= MEM_UNINIT;\n+\n \tif (is_kfunc_arg_nullable(meta-\u003ebtf, \u0026args[arg]))\n \t\targ_type |= PTR_MAYBE_NULL;\n \n+\t/*\n+\t * Only the first argument of a KF_RELEASE kfunc releases anything, and\n+\t * bpf_fetch_kfunc_arg_meta() only ever records BPF_REG_1 for it.\n+\t */\n+\tif (is_kfunc_release(meta) \u0026\u0026 arg == 0)\n+\t\targ_type |= OBJ_RELEASE;\n+\n+\tif (base_type(arg_type) == ARG_PTR_TO_BTF_ID) {\n+\t\tif (is_kfunc_arg_map(meta-\u003ebtf, \u0026args[arg]))\n+\t\t\tproto-\u003earg_btf_id[arg] = reg2btf_ids[CONST_PTR_TO_MAP];\n+\t\telse\n+\t\t\tproto-\u003earg_btf_id[arg] = ref_id_ptr;\n+\n+\t\t/*\n+\t\t * A KF_RCU kfunc accepts an RCU-protected pointer where it would\n+\t\t * otherwise demand a referenced or trusted one. Other argument kinds\n+\t\t * have their own provenance requirements and must not inherit MEM_RCU.\n+\t\t */\n+\t\tif (is_kfunc_rcu(meta))\n+\t\t\targ_type |= MEM_RCU;\n+\t}\n+\n \treturn arg_type;\n }\n \n static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n \t\t\t       struct bpf_func_proto *proto)\n {\n-\tconst struct btf *btf = meta-\u003ebtf;\n \tconst struct btf_param *args;\n \tu32 i, nargs;\n \tint arg_type;\n@@ -12304,47 +12873,38 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg\n \t}\n \n \tfor (i = 0; i \u003c nargs; i++) {\n-\t\tif (is_kfunc_arg_prog_aux(btf, \u0026args[i]) ||\n-\t\t    is_kfunc_arg_ignore(btf, \u0026args[i]) ||\n-\t\t    is_kfunc_arg_implicit(meta, i))\n-\t\t\tcontinue;\n-\n-\t\targ_type = get_kfunc_arg_type(env, meta, args, i, nargs);\n+\t\targ_type = get_kfunc_arg_type(env, meta, args, i, nargs, proto);\n \t\tif (arg_type \u003c 0)\n \t\t\treturn arg_type;\n \n \t\tproto-\u003earg_type[i] = arg_type;\n \t}\n \n-\treturn 0;\n+\treturn check_arg_prog_aux(env, proto) ? 0 : -EINVAL;\n }\n \n-static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,\n-\t\t\t\t\tstruct bpf_reg_state *reg,\n-\t\t\t\t\tconst struct btf_type *ref_t,\n-\t\t\t\t\tconst char *ref_tname, u32 ref_id,\n-\t\t\t\t\tstruct bpf_call_arg_meta *meta,\n-\t\t\t\t\tint arg, argno_t argno)\n+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n+\t\t\t\t     argno_t argno, enum bpf_arg_type arg_type,\n+\t\t\t\t     const struct btf *arg_btf, u32 arg_btf_id,\n+\t\t\t\t     struct bpf_call_arg_meta *meta, int insn_idx)\n {\n-\tconst struct btf_type *reg_ref_t;\n-\tbool strict_type_match = false;\n+\tbool taking_projection, struct_same, strict_type_match = false;\n+\tconst struct btf_type *arg_t, *reg_t;\n+\tconst char *arg_tname, *reg_tname;\n \tconst struct btf *reg_btf;\n-\tconst char *reg_ref_tname;\n-\tbool taking_projection;\n-\tbool struct_same;\n-\tu32 reg_ref_id;\n+\tu32 reg_btf_id;\n \n \tif (base_type(reg-\u003etype) == PTR_TO_BTF_ID) {\n \t\treg_btf = reg-\u003ebtf;\n-\t\treg_ref_id = reg-\u003ebtf_id;\n+\t\treg_btf_id = reg-\u003ebtf_id;\n \t} else {\n \t\treg_btf = btf_vmlinux;\n-\t\treg_ref_id = *reg2btf_ids[base_type(reg-\u003etype)];\n+\t\treg_btf_id = *reg2btf_ids[base_type(reg-\u003etype)];\n \t}\n \n-\t/* Enforce strict type matching for calls to kfuncs that are acquiring\n-\t * or releasing a reference, or are no-cast aliases. We do _not_\n-\t * enforce strict matching for kfuncs by default,\n+\t/*\n+\t * Enforce strict type matching for arguments that release a reference,\n+\t * or are no-cast aliases. We do _not_ enforce strict matching by default,\n \t * as we want to enable BPF programs to pass types that are bitwise\n \t * equivalent without forcing them to explicitly cast with something\n \t * like bpf_cast_to_kern_ctx().\n@@ -12366,27 +12926,30 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,\n \t * btf_struct_ids_match() to walk the struct at the 0th offset, and\n \t * resolve types.\n \t */\n-\tif ((is_kfunc_release(meta) \u0026\u0026 reg_is_referenced(env, reg)) ||\n-\t    btf_type_ids_nocast_alias(\u0026env-\u003elog, reg_btf, reg_ref_id, meta-\u003ebtf, ref_id))\n+\tif ((arg_type_is_release(arg_type) \u0026\u0026 !is_helper_call(meta, BPF_FUNC_sk_release)) ||\n+\t    (meta-\u003ebtf \u0026\u0026 btf_type_ids_nocast_alias(\u0026env-\u003elog, reg_btf, reg_btf_id,\n+\t\t\t\t\t\t    arg_btf, arg_btf_id)))\n \t\tstrict_type_match = true;\n \n-\tWARN_ON_ONCE(is_kfunc_release(meta) \u0026\u0026 !tnum_is_const(reg-\u003evar_off));\n+\targ_t = btf_type_skip_modifiers(arg_btf, arg_btf_id, \u0026arg_btf_id);\n+\targ_tname = btf_name_by_offset(arg_btf, arg_t-\u003ename_off);\n+\treg_t = btf_type_skip_modifiers(reg_btf, reg_btf_id, \u0026reg_btf_id);\n+\treg_tname = btf_name_by_offset(reg_btf, reg_t-\u003ename_off);\n+\n+\tstruct_same = btf_struct_ids_match(\u0026env-\u003elog, reg_btf, reg_btf_id,\n+\t\t\t\t\t  reg-\u003evar_off.value, arg_btf, arg_btf_id,\n+\t\t\t\t\t  strict_type_match, !type_is_alloc(reg-\u003etype));\n \n-\treg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, \u0026reg_ref_id);\n-\treg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t-\u003ename_off);\n-\tstruct_same = btf_struct_ids_match(\u0026env-\u003elog, reg_btf, reg_ref_id, reg-\u003evar_off.value,\n-\t\t\t\t\t   meta-\u003ebtf, ref_id, strict_type_match,\n-\t\t\t\t\t   !type_is_alloc(reg-\u003etype));\n \t/* If kfunc is accepting a projection type (ie. __sk_buff), it cannot\n \t * actually use it -- it must cast to the underlying type. So we allow\n \t * caller to pass in the underlying type.\n \t */\n-\ttaking_projection = btf_is_projection_of(ref_tname, reg_ref_tname);\n+\ttaking_projection = meta-\u003ebtf \u0026\u0026 btf_is_projection_of(arg_tname, reg_tname);\n \tif (!taking_projection \u0026\u0026 !struct_same) {\n-\t\tverbose(env, \"kernel function %s %s expected pointer to %s %s but %s has a pointer to %s %s\\n\",\n+\t\tverbose(env, \"%s %s expected pointer to %s %s but %s has a pointer to %s %s\\n\",\n \t\t\tmeta-\u003efunc_name, reg_arg_name(env, argno),\n-\t\t\tbtf_type_str(ref_t), ref_tname, reg_arg_name(env, argno),\n-\t\t\tbtf_type_str(reg_ref_t), reg_ref_tname);\n+\t\t\tbtf_type_str(arg_t), arg_tname,\n+\t\t\treg_arg_name(env, argno), btf_type_str(reg_t), reg_tname);\n \t\treturn -EINVAL;\n \t}\n \treturn 0;\n@@ -12398,15 +12961,15 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *\n \tint err, spi, kfunc_class = IRQ_NATIVE_KFUNC;\n \tbool irq_save;\n \n-\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_local_irq_save] ||\n-\t    meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) {\n+\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_save]) ||\n+\t    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave])) {\n \t\tirq_save = true;\n-\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])\n+\t\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))\n \t\t\tkfunc_class = IRQ_LOCK_KFUNC;\n-\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_local_irq_restore] ||\n-\t\t   meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]) {\n+\t} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_restore]) ||\n+\t\t   is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])) {\n \t\tirq_save = false;\n-\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])\n+\t\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))\n \t\t\tkfunc_class = IRQ_LOCK_KFUNC;\n \t} else {\n \t\tverifier_bug(env, \"unknown irq flags kfunc\");\n@@ -12599,12 +13162,20 @@ static bool is_bpf_rbtree_api_kfunc(u32 btf_id)\n \t       btf_id == special_kfunc_list[KF_bpf_rbtree_right];\n }\n \n-static bool is_bpf_res_spin_lock_kfunc(u32 btf_id)\n+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta)\n {\n-\treturn btf_id == special_kfunc_list[KF_bpf_res_spin_lock] ||\n-\t       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock] ||\n-\t       btf_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||\n-\t       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore];\n+\tint flags = PROCESS_RES_LOCK;\n+\n+\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock]) ||\n+\t    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))\n+\t\tflags |= PROCESS_SPIN_LOCK;\n+\telse if (!is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock]) \u0026\u0026\n+\t\t !is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))\n+\t\treturn 0;\n+\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) ||\n+\t    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))\n+\t\tflags |= PROCESS_LOCK_IRQ;\n+\treturn flags;\n }\n \n static bool kfunc_spin_allowed(struct bpf_verifier_env *env, s32 func_id, s16 offset)\n@@ -12881,707 +13452,6 @@ static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env)\n \t}\n }\n \n-static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n-\t\t\t    int insn_idx)\n-{\n-\tconst char *func_name = meta-\u003efunc_name, *ref_tname;\n-\tstruct bpf_func_state *caller = cur_func(env);\n-\tstruct bpf_reg_state *regs = cur_regs(env);\n-\tconst struct btf *btf = meta-\u003ebtf;\n-\tconst struct btf_param *args;\n-\tstruct btf_record *rec;\n-\tu32 i, nargs;\n-\tint ret;\n-\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, func_name, btf, args);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/* Check that BTF function arguments match actual types that the\n-\t * verifier sees.\n-\t */\n-\tfor (i = 0; i \u003c nargs; i++) {\n-\t\tstruct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);\n-\t\tconst struct btf_type *t, *ref_t, *resolve_ret;\n-\t\tenum bpf_arg_type arg_type = ARG_DONTCARE;\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 = 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-\t\t\t/* Reject repeated use bpf_prog_aux */\n-\t\t\tif (meta-\u003earg_prog) {\n-\t\t\t\tverifier_bug(env, \"Only 1 prog-\u003eaux argument supported per-kfunc\");\n-\t\t\t\treturn -EFAULT;\n-\t\t\t}\n-\t\t\tif (regno \u003c 0) {\n-\t\t\t\tverbose(env, \"%s prog-\u003eaux cannot be a stack argument\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tmeta-\u003earg_prog = true;\n-\t\t\tcur_aux(env)-\u003earg_prog = regno;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (is_kfunc_arg_ignore(btf, \u0026args[i]) || is_kfunc_arg_implicit(meta, i))\n-\t\t\tcontinue;\n-\n-\t\tt = btf_type_skip_modifiers(btf, args[i].type, NULL);\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-\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, args[i].type);\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 value of type %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 (bpf_register_is_null(reg) \u0026\u0026 type_may_be_null(kf_arg_type)) {\n-\t\t\tret = mark_arg_precision(env, argno);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (is_kfunc_arg_map(btf, \u0026args[i])) {\n-\t\t\tref_id = *reg2btf_ids[CONST_PTR_TO_MAP];\n-\t\t\tref_t = btf_type_by_id(btf_vmlinux, ref_id);\n-\t\t\tref_tname = btf_name_by_offset(btf, ref_t-\u003ename_off);\n-\t\t}\n-\n-\t\tswitch (base_type(kf_arg_type)) {\n-\t\tcase KF_ARG_CONST:\n-\t\tcase KF_ARG_CONST_MEM_SIZE:\n-\t\tcase KF_ARG_MEM_SIZE:\n-\t\tcase KF_ARG_ANYTHING:\n-\t\tcase KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:\n-\t\tcase KF_ARG_PTR_TO_ALLOC_BTF_ID:\n-\t\tcase KF_ARG_PTR_TO_BTF_ID:\n-\t\tcase KF_ARG_CONST_MAP_PTR:\n-\t\tcase KF_ARG_PTR_TO_ITER:\n-\t\tcase KF_ARG_PTR_TO_LIST_HEAD:\n-\t\tcase KF_ARG_PTR_TO_LIST_NODE:\n-\t\tcase KF_ARG_PTR_TO_RB_ROOT:\n-\t\tcase KF_ARG_PTR_TO_RB_NODE:\n-\t\tcase KF_ARG_PTR_TO_MEM:\n-\t\tcase KF_ARG_PTR_TO_CALLBACK:\n-\t\tcase KF_ARG_PTR_TO_CONST_STR:\n-\t\tcase KF_ARG_PTR_TO_WORKQUEUE:\n-\t\tcase KF_ARG_PTR_TO_TIMER:\n-\t\tcase KF_ARG_PTR_TO_TASK_WORK:\n-\t\tcase KF_ARG_PTR_TO_IRQ_FLAG:\n-\t\tcase KF_ARG_PTR_TO_RES_SPIN_LOCK:\n-\t\tcase KF_ARG_PTR_TO_ARENA:\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_DYNPTR:\n-\t\t\targ_type = ARG_PTR_TO_DYNPTR;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_CTX:\n-\t\t\targ_type = ARG_PTR_TO_CTX;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_REFCOUNTED_KPTR:\n-\t\t\targ_type = ARG_PTR_TO_BTF_ID;\n-\t\t\tbtf_id_fixed_off_ok = false;\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tverifier_bug(env, \"unknown kfunc arg type %d\", kf_arg_type);\n-\t\t\treturn -EFAULT;\n-\t\t}\n-\n-\t\tif (regno == meta-\u003erelease_regno)\n-\t\t\targ_type |= OBJ_RELEASE;\n-\t\tret = __check_func_arg_reg_off(env, reg, argno, arg_type,\n-\t\t\t\t\t       btf_id_fixed_off_ok);\n-\t\tif (ret \u003c 0)\n-\t\t\treturn ret;\n-\n-\t\tswitch (base_type(kf_arg_type)) {\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\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\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-\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {\n-\t\t\t\tret = get_kern_ctx_btf_id(\u0026env-\u003elog, resolve_prog_type(env-\u003eprog));\n-\t\t\t\tif (ret \u003c 0)\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\tmeta-\u003eret_btf_id  = ret;\n-\t\t\t}\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_ARENA:\n-\t\t\tif (reg-\u003etype != PTR_TO_ARENA \u0026\u0026 reg-\u003etype != SCALAR_VALUE) {\n-\t\t\t\tverbose(env, \"%s is not a pointer to arena or scalar\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_ALLOC_BTF_ID:\n-\t\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC)) {\n-\t\t\t\tif (!is_bpf_obj_drop_kfunc(meta-\u003efunc_id)) {\n-\t\t\t\t\tverbose(env, \"%s expected for bpf_obj_drop()\\n\",\n-\t\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t} else if (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {\n-\t\t\t\tif (!is_bpf_percpu_obj_drop_kfunc(meta-\u003efunc_id)) {\n-\t\t\t\t\tverbose(env, \"%s expected for bpf_percpu_obj_drop()\\n\",\n-\t\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\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-\t\t\t\tmeta-\u003earg_btf = reg-\u003ebtf;\n-\t\t\t\tmeta-\u003earg_btf_id = reg-\u003ebtf_id;\n-\t\t\t}\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_DYNPTR:\n-\t\t{\n-\t\t\tenum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;\n-\n-\t\t\tif (is_kfunc_arg_uninit(btf, \u0026args[i]))\n-\t\t\t\tdynptr_arg_type |= MEM_UNINIT;\n-\n-\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) {\n-\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_SKB;\n-\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) {\n-\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_XDP;\n-\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) {\n-\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_SKB_META;\n-\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {\n-\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_FILE;\n-\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {\n-\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;\n-\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_clone] \u0026\u0026\n-\t\t\t\t   (dynptr_arg_type \u0026 MEM_UNINIT)) {\n-\t\t\t\tenum bpf_dynptr_type parent_type = meta-\u003edynptr.type;\n-\n-\t\t\t\tif (parent_type == BPF_DYNPTR_TYPE_INVALID) {\n-\t\t\t\t\tverifier_bug(env, \"no dynptr type for parent of clone\");\n-\t\t\t\t\treturn -EFAULT;\n-\t\t\t\t}\n-\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, 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-\t\t}\n-\t\tcase KF_ARG_PTR_TO_ITER:\n-\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_iter_css_task_new]) {\n-\t\t\t\tif (!check_css_task_iter_allowlist(env)) {\n-\t\t\t\t\tverbose(env, \"css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\\n\");\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tret = process_iter_arg(env, reg, argno, insn_idx, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_LIST_HEAD:\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE \u0026\u0026\n-\t\t\t    reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\n-\t\t\t\tverbose(env, \"%s expected pointer to map value or allocated object\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC) \u0026\u0026\n-\t\t\t    !reg_is_referenced(env, reg)) {\n-\t\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_RB_ROOT:\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE \u0026\u0026\n-\t\t\t    reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\n-\t\t\t\tverbose(env, \"%s expected pointer to map value or allocated object\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC) \u0026\u0026\n-\t\t\t    !reg_is_referenced(env, reg)) {\n-\t\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_LIST_NODE:\n-\t\t\tif (is_kfunc_arg_nonown_allowed(btf, \u0026args[i]) \u0026\u0026\n-\t\t\t    type_is_non_owning_ref(reg-\u003etype) \u0026\u0026 !reg_is_referenced(env, reg)) {\n-\t\t\t\t/* Allow bpf_list_front/back return value for\n-\t\t\t\t * __nonown_allowed list-node arguments.\n-\t\t\t\t */\n-\t\t\t\tgoto check_ok;\n-\t\t\t}\n-\t\t\tif (reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\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\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\treturn -EINVAL;\n-\t\t\t}\n-check_ok:\n-\t\t\tret = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_RB_NODE:\n-\t\t\tif (is_bpf_rbtree_add_kfunc(meta-\u003efunc_id)) {\n-\t\t\t\tif (reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\n-\t\t\t\t\tverbose(env, \"%s expected pointer to allocated object\\n\",\n-\t\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t\tif (!reg_is_referenced(env, reg)) {\n-\t\t\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t} else {\n-\t\t\t\tif (!type_is_non_owning_ref(reg-\u003etype) \u0026\u0026\n-\t\t\t\t    !reg_is_referenced(env, reg)) {\n-\t\t\t\t\tverbose(env, \"%s can only take non-owning or refcounted bpf_rb_node pointer\\n\", func_name);\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t\tif (in_rbtree_lock_required_cb(env)) {\n-\t\t\t\t\tverbose(env, \"%s not allowed in rbtree cb\\n\", func_name);\n-\t\t\t\t\treturn -EINVAL;\n-\t\t\t\t}\n-\t\t\t}\n-\n-\t\t\tret = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_CONST_MAP_PTR:\n-\t\t\tif (base_type(reg-\u003etype) != CONST_PTR_TO_MAP ||\n-\t\t\t    type_may_be_null(reg-\u003etype)) {\n-\t\t\t\tverbose(env, \"pointer in %s isn't map pointer\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = process_map_ptr_arg(env, reg, argno, meta);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_BTF_ID:\n-\t\t\t/* Only base_type is checked, further checks are done here */\n-\t\t\tif (base_type(reg-\u003etype) == PTR_TO_BTF_ID ||\n-\t\t\t    reg2btf_ids[base_type(reg-\u003etype)]) {\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-\n-\t\t\t\tret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);\n-\t\t\t\tif (ret \u003c 0)\n-\t\t\t\t\treturn ret;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\n-\t\t\tif (!btf_type_is_scalar_struct(env, meta-\u003ebtf, ref_t)) {\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-\t\t\t\t\tbtf_type_str(ref_t), ref_tname);\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-\t\t\t/*\n-\t\t\t * If the register does not contain btf id but the argument type is a pointer to\n-\t\t\t * scalar-only struct, allow verifying it as a fixed size memory.\n-\t\t\t */\n-\t\t\tkf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\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-\t\t\t\t\t\treg_arg_name(env, argno), btf_type_str(ref_t),\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,\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-\t\t\tret = process_const_arg(env, reg, argno, meta);\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 memory size 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\tfallthrough;\n-\t\tcase KF_ARG_MEM_SIZE:\n-\t\t{\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-\t\t\tif (bpf_register_is_null(buff_reg))\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, \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-\t\t}\n-\t\tcase KF_ARG_PTR_TO_CALLBACK:\n-\t\t\tif (reg-\u003etype != PTR_TO_FUNC) {\n-\t\t\t\tverbose(env, \"%s expected pointer to func\\n\", reg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tmeta-\u003esubprogno = reg-\u003esubprogno;\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\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 an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field.\",\n-\t\t\t\t\t\t      \"the kfunc expects a pointer to a BPF-managed refcounted object, 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 (!type_is_non_owning_ref(reg-\u003etype) \u0026\u0026 reg_is_referenced(env, reg))\n-\t\t\t\tmeta-\u003earg_owning_ref = true;\n-\n-\t\t\trec = reg_btf_record(reg);\n-\t\t\tif (!rec) {\n-\t\t\t\tverifier_bug(env, \"Couldn't find btf_record\");\n-\t\t\t\treturn -EFAULT;\n-\t\t\t}\n-\n-\t\t\tif (rec-\u003erefcount_off \u003c 0) {\n-\t\t\t\tverbose(env, \"%s doesn't point to a type with bpf_refcount field\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\n-\t\t\tmeta-\u003earg_btf = reg-\u003ebtf;\n-\t\t\tmeta-\u003earg_btf_id = reg-\u003ebtf_id;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_CONST_STR:\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-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_WORKQUEUE:\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE) {\n-\t\t\t\tverbose(env, \"%s doesn't point to a map value\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, \u0026meta-\u003emap);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_TIMER:\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE) {\n-\t\t\t\tverbose(env, \"%s doesn't point to a map value\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = process_timer_func(env, reg, argno, \u0026meta-\u003emap);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_TASK_WORK:\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE) {\n-\t\t\t\tverbose(env, \"%s doesn't point to a map value\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\t\t\tret = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, \u0026meta-\u003emap);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_IRQ_FLAG:\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-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\tcase KF_ARG_PTR_TO_RES_SPIN_LOCK:\n-\t\t{\n-\t\t\tint flags = PROCESS_RES_LOCK;\n-\n-\t\t\tif (in_rbtree_lock_required_cb(env)) {\n-\t\t\t\tverbose(env, \"can't res_spin_{lock,unlock} in rbtree cb\\n\");\n-\t\t\t\treturn -EACCES;\n-\t\t\t}\n-\n-\t\t\tif (reg-\u003etype != PTR_TO_MAP_VALUE \u0026\u0026 reg-\u003etype != (PTR_TO_BTF_ID | MEM_ALLOC)) {\n-\t\t\t\tverbose(env, \"%s doesn't point to map value or allocated object\\n\",\n-\t\t\t\t\treg_arg_name(env, argno));\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n-\n-\t\t\tif (!is_bpf_res_spin_lock_kfunc(meta-\u003efunc_id))\n-\t\t\t\treturn -EFAULT;\n-\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_lock] ||\n-\t\t\t    meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])\n-\t\t\t\tflags |= PROCESS_SPIN_LOCK;\n-\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||\n-\t\t\t    meta-\u003efunc_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])\n-\t\t\t\tflags |= PROCESS_LOCK_IRQ;\n-\t\t\tret = process_spin_lock(env, reg, argno, flags);\n-\t\t\tif (ret \u003c 0)\n-\t\t\t\treturn ret;\n-\t\t\tbreak;\n-\t\t}\n-\t\t}\n-\t}\n-\n-\treturn 0;\n-}\n-\n int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,\n \t\t\t     s32 func_id,\n \t\t\t     s16 offset,\n@@ -13912,12 +13782,12 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg\n \t\tstruct btf_field *field = meta-\u003earg_rbtree_root.field;\n \n \t\tmark_reg_graph_node(regs, BPF_REG_0, \u0026field-\u003egraph_root);\n-\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {\n+\t} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx])) {\n \t\tmark_reg_known_zero(env, regs, BPF_REG_0);\n \t\tregs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_TRUSTED;\n \t\tregs[BPF_REG_0].btf = desc_btf;\n \t\tregs[BPF_REG_0].btf_id = meta-\u003eret_btf_id;\n-\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_rdonly_cast]) {\n+\t} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_rdonly_cast])) {\n \t\tret_t = btf_type_by_id(desc_btf, meta-\u003earg_constant.value);\n \t\tif (!ret_t) {\n \t\t\tverbose(env, \"Unknown type ID %lld passed to kfunc bpf_rdonly_cast\\n\",\n@@ -13937,8 +13807,8 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg\n \t\t\t\t\"kfunc bpf_rdonly_cast type ID argument must be of a struct or void\\n\");\n \t\t\treturn -EINVAL;\n \t\t}\n-\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_slice] ||\n-\t\t   meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {\n+\t} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice]) ||\n+\t\t   is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice_rdwr])) {\n \t\tenum bpf_type_flag type_flag = get_dynptr_type_flag(meta-\u003edynptr.type);\n \n \t\tmark_reg_known_zero(env, regs, BPF_REG_0);\n@@ -13953,7 +13823,7 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg\n \t\t/* PTR_MAYBE_NULL will be added when is_kfunc_ret_null is checked */\n \t\tregs[BPF_REG_0].type = PTR_TO_MEM | type_flag;\n \n-\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_slice]) {\n+\t\tif (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice])) {\n \t\t\tregs[BPF_REG_0].type |= MEM_RDONLY;\n \t\t} else {\n \t\t\t/* this will set env-\u003eseen_direct_write to true */\n@@ -14073,7 +13943,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n \t\tinsn_aux-\u003enon_sleepable = true;\n \n \t/* Check the arguments */\n-\terr = check_kfunc_args(env, \u0026meta, insn_idx);\n+\terr = check_func_args(env, \u0026meta, insn_idx);\n \tif (err \u003c 0)\n \t\treturn err;\n \n@@ -17972,7 +17842,7 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,\n \t\tcs-\u003eis_void = fn-\u003eret_type == RET_VOID;\n \t\tcs-\u003enum_params = 0;\n \t\tfor (i = 0; i \u003c ARRAY_SIZE(fn-\u003earg_type); ++i) {\n-\t\t\tif (fn-\u003earg_type[i] == ARG_DONTCARE)\n+\t\t\tif (fn-\u003earg_type[i] == ARG_UNUSED)\n \t\t\t\tbreak;\n \t\t\tcs-\u003enum_params++;\n \t\t}\n@@ -19776,7 +19646,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)\n \t\t\t}\n \n \t\t\t/* Also ensure the callback only has a single scalar argument. */\n-\t\t\tif (sub-\u003earg_cnt != 1 || sub-\u003eargs[0].arg_type != ARG_ANYTHING) {\n+\t\t\tif (sub-\u003earg_cnt != 1 || sub-\u003eargs[0].arg_type != ARG_SCALAR) {\n \t\t\t\tverbose(env, \"exception cb only supports single integer argument\\n\");\n \t\t\t\tret = -EINVAL;\n \t\t\t\tgoto out;\n@@ -19789,7 +19659,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)\n \t\t\tif (arg-\u003earg_type == ARG_PTR_TO_CTX) {\n \t\t\t\treg-\u003etype = PTR_TO_CTX;\n \t\t\t\tmark_reg_known_zero(env, regs, i);\n-\t\t\t} else if (arg-\u003earg_type == ARG_ANYTHING) {\n+\t\t\t} else if (arg-\u003earg_type == ARG_SCALAR) {\n \t\t\t\treg-\u003etype = SCALAR_VALUE;\n \t\t\t\tmark_reg_unknown(env, regs, i);\n \t\t\t} else if (arg-\u003earg_type == ARG_PTR_TO_DYNPTR) {\ndiff --git a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c\nindex 14d4c1793aed5..d74a9db54c9a3 100644\n--- a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c\n+++ b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c\n@@ -13,13 +13,13 @@ struct {\n \tconst char *prog_name;\n \tconst char *err_msg;\n } test_bpf_nf_fail_tests[] = {\n-\t{ \"alloc_release\", \"kernel function bpf_ct_release R1 expected pointer to STRUCT nf_conn but\" },\n-\t{ \"insert_insert\", \"kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but\" },\n-\t{ \"lookup_insert\", \"kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but\" },\n-\t{ \"set_timeout_after_insert\", \"kernel function bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but\" },\n-\t{ \"set_status_after_insert\", \"kernel function bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but\" },\n-\t{ \"change_timeout_after_alloc\", \"kernel function bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but\" },\n-\t{ \"change_status_after_alloc\", \"kernel function bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but\" },\n+\t{ \"alloc_release\", \"bpf_ct_release R1 expected pointer to STRUCT nf_conn but\" },\n+\t{ \"insert_insert\", \"bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but\" },\n+\t{ \"lookup_insert\", \"bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but\" },\n+\t{ \"set_timeout_after_insert\", \"bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but\" },\n+\t{ \"set_status_after_insert\", \"bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but\" },\n+\t{ \"change_timeout_after_alloc\", \"bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but\" },\n+\t{ \"change_status_after_alloc\", \"bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but\" },\n \t{ \"write_not_allowlisted_field\", \"no write support to nf_conn at off\" },\n \t{ \"lookup_null_bpf_tuple\", \"Possibly NULL pointer passed to trusted R2\" },\n \t{ \"lookup_null_bpf_opts\", \"Possibly NULL pointer passed to trusted R4\" },\ndiff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c\nindex 78566b817fd70..c32c6dab49bce 100644\n--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c\n+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c\n@@ -11,8 +11,8 @@ struct {\n \tconst char *prog_name;\n \tconst char *err_msg;\n } cb_refs_tests[] = {\n-\t{ \"underflow_prog\", \"release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1\" },\n-\t{ \"leak_prog\", \"Possibly NULL pointer passed to helper R2\" },\n+\t{ \"underflow_prog\", \"R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_\" },\n+\t{ \"leak_prog\", \"Unreleased reference id=4 alloc_insn=3\" }, /* alloc_insn=3{2,3} */\n \t{ \"nested_cb\", \"Unreleased reference id=4 alloc_insn=2\" }, /* alloc_insn=2{4,5} */\n \t{ \"non_cb_transfer_ref\", \"Unreleased reference id=4 alloc_insn=1\" }, /* alloc_insn=1{1,2} */\n };\ndiff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c\nindex 2b39cc1b09f9a..0063e60d6f2f5 100644\n--- a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c\n+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c\n@@ -70,7 +70,7 @@ static struct kfunc_test_params kfunc_tests[] = {\n \tTC_FAIL(kfunc_call_test_get_mem_fail_oversized, 0, \"allocation size exceeds u32 max\"),\n \tTC_FAIL(kfunc_call_test_get_mem_fail_not_const, 0, \"is not a const\"),\n \tTC_FAIL(kfunc_call_test_mem_acquire_fail, 0, \"acquire kernel function does not return PTR_TO_BTF_ID\"),\n-\tTC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, \"R1 expected pointer to ctx, but got scalar\"),\n+\tTC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, \"R1 type=scalar expected=ctx\"),\n \tTC_FAIL(kfunc_call_test_spin_lock_unsafe, 0, \"function calls are not allowed while holding a lock\"),\n \n \t/* success cases */\ndiff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c\nindex f7f94ccebce27..b973814482481 100644\n--- a/tools/testing/selftests/bpf/prog_tests/verifier.c\n+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c\n@@ -54,6 +54,7 @@\n #include \"verifier_iterating_callbacks.skel.h\"\n #include \"verifier_jeq_infer_not_null.skel.h\"\n #include \"verifier_jit_convergence.skel.h\"\n+#include \"verifier_kfunc_packet_access.skel.h\"\n #include \"verifier_ld_ind.skel.h\"\n #include \"verifier_ldsx.skel.h\"\n #include \"verifier_leak_ptr.skel.h\"\n@@ -218,6 +219,7 @@ void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }\n void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }\n void test_verifier_jeq_infer_not_null(void)   { RUN(verifier_jeq_infer_not_null); }\n void test_verifier_jit_convergence(void)      { RUN(verifier_jit_convergence); }\n+void test_verifier_kfunc_packet_access(void)  { RUN_TESTS(verifier_kfunc_packet_access); }\n void test_verifier_load_acquire(void)         { RUN(verifier_load_acquire); }\n void test_verifier_ld_ind(void)               { RUN(verifier_ld_ind); }\n void test_verifier_ldsx(void)                  { RUN(verifier_ldsx); }\ndiff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c\nindex 50609f3b0564a..6578cf12fa27d 100644\n--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c\n+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c\n@@ -205,7 +205,7 @@ int arena_arg_no_arena(void *ctx)\n SEC(\"syscall\")\n __arch_x86_64\n __arch_arm64\n-__failure __msg(\"is not a pointer to arena or scalar\")\n+__failure __msg(\"R1 type=fp expected=arena, scalar\")\n int arena_arg_bad_reg(void *ctx)\n {\n \tu64 buf = 0;\ndiff --git a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c\nindex efe7bcae70f85..ede6a17d7da30 100644\n--- a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c\n+++ b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c\n@@ -64,7 +64,7 @@ int BPF_PROG(cgrp_kfunc_acquire_no_null_check, struct cgroup *cgrp, const char *\n }\n \n SEC(\"tp_btf/cgroup_mkdir\")\n-__failure __msg(\"R1 is fp expected STRUCT cgroup\")\n+__failure __msg(\"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int BPF_PROG(cgrp_kfunc_acquire_fp, struct cgroup *cgrp, const char *path)\n {\n \tstruct cgroup *acquired, *stack_cgrp = (struct cgroup *)\u0026path;\n@@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)\n }\n \n SEC(\"tp_btf/cgroup_mkdir\")\n-__failure __msg(\"release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)\n {\n \tstruct cgroup *kptr;\n@@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path\n }\n \n SEC(\"tp_btf/cgroup_mkdir\")\n-__failure __msg(\"release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)\n {\n \tstruct cgroup *acquired = (struct cgroup *)\u0026path;\n@@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)\n }\n \n SEC(\"tp_btf/cgroup_mkdir\")\n-__failure __msg(\"release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)\n {\n \t/* Cannot release trusted cgroup pointer which was not acquired. */\ndiff --git a/tools/testing/selftests/bpf/progs/cpumask_failure.c b/tools/testing/selftests/bpf/progs/cpumask_failure.c\nindex 4628feb53d861..c89c88db39d14 100644\n--- a/tools/testing/selftests/bpf/progs/cpumask_failure.c\n+++ b/tools/testing/selftests/bpf/progs/cpumask_failure.c\n@@ -183,7 +183,7 @@ int BPF_PROG(test_global_mask_no_null_check, struct task_struct *task, u64 clone\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"Possibly NULL pointer passed to helper R2\")\n+__failure __msg(\"release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2\")\n int BPF_PROG(test_global_mask_rcu_no_null_check, struct task_struct *task, u64 clone_flags)\n {\n \tstruct bpf_cpumask *prev, *curr;\n@@ -243,7 +243,7 @@ int BPF_PROG(test_populate_invalid_destination, struct task_struct *task, u64 cl\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"leads to invalid memory access\")\n+__failure __msg(\"R2 type=scalar expected=fp\")\n int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_flags)\n {\n \tvoid *garbage = (void *)0x123456;\ndiff --git a/tools/testing/selftests/bpf/progs/irq.c b/tools/testing/selftests/bpf/progs/irq.c\nindex a4a007866a332..53df6d248e267 100644\n--- a/tools/testing/selftests/bpf/progs/irq.c\n+++ b/tools/testing/selftests/bpf/progs/irq.c\n@@ -15,7 +15,7 @@ struct bpf_res_spin_lock lockA __hidden SEC(\".data.A\");\n struct bpf_res_spin_lock lockB __hidden SEC(\".data.B\");\n \n SEC(\"?tc\")\n-__failure __msg(\"R1 doesn't point to an irq flag on stack\")\n+__failure __msg(\"R1 type=map_value expected=fp\")\n int irq_save_bad_arg(struct __sk_buff *ctx)\n {\n \tbpf_local_irq_save(\u0026global_flags);\n@@ -23,7 +23,7 @@ int irq_save_bad_arg(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"R1 doesn't point to an irq flag on stack\")\n+__failure __msg(\"R1 type=map_value expected=fp\")\n int irq_restore_bad_arg(struct __sk_buff *ctx)\n {\n \tbpf_local_irq_restore(\u0026global_flags);\ndiff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c\nindex c6699159dacdd..65d4c6e01f932 100644\n--- a/tools/testing/selftests/bpf/progs/iters.c\n+++ b/tools/testing/selftests/bpf/progs/iters.c\n@@ -1688,7 +1688,7 @@ int iter_subprog_check_stacksafe(const void *ctx)\n struct bpf_iter_num global_it;\n \n SEC(\"raw_tp\")\n-__failure __msg(\"R1 expected pointer to an iterator on stack\")\n+__failure __msg(\"R1 type=map_value expected=fp\")\n int iter_new_bad_arg(const void *ctx)\n {\n \tbpf_iter_num_new(\u0026global_it, 0, 1);\n@@ -1696,7 +1696,7 @@ int iter_new_bad_arg(const void *ctx)\n }\n \n SEC(\"raw_tp\")\n-__failure __msg(\"R1 expected pointer to an iterator on stack\")\n+__failure __msg(\"R1 type=map_value expected=fp\")\n int iter_next_bad_arg(const void *ctx)\n {\n \tbpf_iter_num_next(\u0026global_it);\n@@ -1704,7 +1704,7 @@ int iter_next_bad_arg(const void *ctx)\n }\n \n SEC(\"raw_tp\")\n-__failure __msg(\"R1 expected pointer to an iterator on stack\")\n+__failure __msg(\"R1 type=map_value expected=fp\")\n int iter_destroy_bad_arg(const void *ctx)\n {\n \tbpf_iter_num_destroy(\u0026global_it);\ndiff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c\nindex 76012dbbdb413..f65cc9766633e 100644\n--- a/tools/testing/selftests/bpf/progs/iters_testmod.c\n+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c\n@@ -105,8 +105,7 @@ int iter_next_rcu_not_trusted(const void *ctx)\n }\n \n SEC(\"raw_tp/sys_enter\")\n-__failure __msg(\"R1 cannot write into rdonly_mem\")\n-/* Message should not be 'R1 cannot write into rdonly_trusted_mem' */\n+__failure __msg(\"R1 type=rdonly_mem expected=fp\")\n int iter_next_ptr_mem_not_trusted(const void *ctx)\n {\n \tstruct bpf_iter_num num_it;\n@@ -135,7 +134,7 @@ int iter_ret_rcu_test_protected(const void *ctx)\n }\n \n SEC(\"?fentry.s/\" SYS_PREFIX \"sys_getpgid\")\n-__failure __msg(\"R1 type=rcu_ptr_or_null_ expected=\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n int iter_ret_rcu_test_type(const void *ctx)\n {\n \tstruct task_struct *p;\n@@ -158,7 +157,7 @@ int iter_ret_rcu_test_protected_nostruct(const void *ctx)\n }\n \n SEC(\"?fentry.s/\" SYS_PREFIX \"sys_getpgid\")\n-__failure __msg(\"R1 type=rdonly_rcu_mem_or_null expected=\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n int iter_ret_rcu_test_type_nostruct(const void *ctx)\n {\n \tvoid *p;\ndiff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c\nindex eee35d203b66f..ac4003bfb8b09 100644\n--- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c\n+++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c\n@@ -149,7 +149,7 @@ int reject_bad_type_match(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"R1 type=untrusted_ptr_or_null_ expected=percpu_ptr_\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n int marked_as_untrusted_or_null(struct __sk_buff *ctx)\n {\n \tstruct map_value *v;\n@@ -217,7 +217,7 @@ int reject_kptr_xchg_on_unref(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"R1 type=rcu_ptr_or_null_ expected=percpu_ptr_\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n int mark_ref_as_untrusted_or_null(struct __sk_buff *ctx)\n {\n \tstruct map_value *v;\n@@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2\")\n+__failure __msg(\"release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2\")\n int reject_untrusted_xchg(struct __sk_buff *ctx)\n {\n \tstruct prog_test_ref_kfunc *p;\n@@ -291,7 +291,7 @@ int reject_bad_type_xchg(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"invalid kptr access, R2 type=trusted_ptr_prog_test_ref_kfunc\")\n+__failure __msg(\"R2 must have zero offset when passed to release func\")\n int reject_member_of_ref_xchg(struct __sk_buff *ctx)\n {\n \tstruct prog_test_ref_kfunc *ref_ptr;\n@@ -364,7 +364,7 @@ int kptr_xchg_ref_state(struct __sk_buff *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"Possibly NULL pointer passed to helper R2\")\n+__success\n int kptr_xchg_possibly_null(struct __sk_buff *ctx)\n {\n \tstruct prog_test_ref_kfunc *p;\ndiff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c\nindex 3e0d4f687aaad..23019023511af 100644\n--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c\n+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c\n@@ -118,8 +118,7 @@ int atomic_rmw_not_ok(void *ctx)\n \n SEC(\"socket\")\n __failure\n-__msg(\"invalid access to memory, mem_size=0 off=0 size=4\")\n-__msg(\"R1 min value is outside of the allowed memory range\")\n+__msg(\"R1 type=rdonly_untrusted_mem expected=fp\")\n int kfunc_param_not_ok(void *ctx)\n {\n \tint *p;\ndiff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c\nindex 3701f4ea58c75..57615b0f0c25d 100644\n--- a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c\n+++ b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c\n@@ -24,6 +24,7 @@ struct val_600b_t {\n struct elem {\n \tlong sum;\n \tstruct val_t __percpu_kptr *pc;\n+\tstruct val_t __percpu_kptr *pc2;\n };\n \n struct {\n@@ -46,6 +47,8 @@ struct {\n \n struct task_struct *bpf_task_from_pid(s32 pid) __ksym;\n void bpf_task_release(struct task_struct *p) __ksym;\n+void bpf_rcu_read_lock(void) __ksym;\n+void bpf_rcu_read_unlock(void) __ksym;\n \n long ret;\n \n@@ -123,6 +126,38 @@ int BPF_PROG(test_array_map_3)\n \treturn 0;\n }\n \n+SEC(\"?fentry.s/bpf_fentry_test1\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n+int BPF_PROG(reject_nullable_percpu_xchg_alias)\n+{\n+\tstruct val_t __percpu_kptr *p1, *p2, *old;\n+\tstruct val_t *v;\n+\tstruct elem *e;\n+\tint index = 0;\n+\n+\te = bpf_map_lookup_elem(\u0026array, \u0026index);\n+\tif (!e)\n+\t\treturn 0;\n+\n+\tp1 = bpf_percpu_obj_new(struct val_t);\n+\tp2 = bpf_percpu_obj_new(struct val_t);\n+\n+\tbpf_rcu_read_lock();\n+\told = bpf_kptr_xchg(\u0026e-\u003epc, p1);\n+\tif (old)\n+\t\tbpf_percpu_obj_drop(old);\n+\told = bpf_kptr_xchg(\u0026e-\u003epc2, p2);\n+\tif (old)\n+\t\tbpf_percpu_obj_drop(old);\n+\n+\tif (p1) {\n+\t\tv = bpf_this_cpu_ptr(p2);\n+\t\tv-\u003eb = 1;\n+\t}\n+\tbpf_rcu_read_unlock();\n+\treturn 0;\n+}\n+\n SEC(\"?fentry.s/bpf_fentry_test1\")\n __failure __msg(\"R1 expected for bpf_percpu_obj_drop()\")\n int BPF_PROG(test_array_map_4)\ndiff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c\nindex 4504608196abd..08709f23ec0f1 100644\n--- a/tools/testing/selftests/bpf/progs/rbtree_fail.c\n+++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c\n@@ -180,7 +180,7 @@ long rbtree_api_use_unchecked_remove_retval(void *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer\")\n+__failure __msg(\"R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n long rbtree_api_add_release_unlock_escape(void *ctx)\n {\n \tstruct node_data *n;\n@@ -204,7 +204,7 @@ long rbtree_api_add_release_unlock_escape(void *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer\")\n+__failure __msg(\"R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n long rbtree_api_first_release_unlock_escape(void *ctx)\n {\n \tstruct bpf_rb_node *res;\ndiff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c\nindex 338e43822ffec..e80f78fae2276 100644\n--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c\n+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c\n@@ -118,8 +118,8 @@ long refcount_acquire_maybe_null(void *ctx)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"R1 is neither owning or non-owning ref\")\n-__msg(\"expects a pointer to a BPF-managed refcounted object, but R1 is a context pointer\")\n+__failure __msg(\"R1 type=ctx expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n+__msg(\"type ctx, but this argument accepts ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n long refcount_acquire_non_object(void *ctx)\n {\n \treturn bpf_refcount_acquire(ctx) != NULL;\n@@ -159,8 +159,7 @@ long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx)\n \n SEC(\"?syscall\")\n __failure\n-__msg(\"bpf_rbtree_remove can only take non-owning or refcounted \"\n-      \"bpf_rb_node pointer\")\n+__msg(\"R2 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n long rbtree_remove_after_rcu_unlock(void *ctx)\n {\n \tstruct map_value_rcu_graph *mapval;\n@@ -190,7 +189,7 @@ long rbtree_remove_after_rcu_unlock(void *ctx)\n }\n \n SEC(\"?syscall\")\n-__failure __msg(\"R1 is neither owning or non-owning ref\")\n+__failure __msg(\"R1 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_\")\n long refcount_acquire_after_rcu_unlock(void *ctx)\n {\n \tstruct map_value_refcount_only *mapval;\ndiff --git a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c\nindex 330682a88c161..8fd591bd1f6cf 100644\n--- a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c\n+++ b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c\n@@ -24,7 +24,7 @@ struct bpf_spin_lock lock __hidden SEC(\".data.A\");\n struct bpf_res_spin_lock res_lock __hidden SEC(\".data.B\");\n \n SEC(\"?tc\")\n-__failure __msg(\"point to map value or allocated object\")\n+__failure __msg(\"R1 type=untrusted_ptr_ expected=map_value, ptr_\")\n int res_spin_lock_arg(struct __sk_buff *ctx)\n {\n \tstruct arr_elem *elem;\ndiff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c\nindex 21428bb1ee597..10ebb4a7f105a 100644\n--- a/tools/testing/selftests/bpf/progs/stream_fail.c\n+++ b/tools/testing/selftests/bpf/progs/stream_fail.c\n@@ -23,7 +23,7 @@ int stream_vprintk_scalar_arg(void *ctx)\n }\n \n SEC(\"syscall\")\n-__failure __msg(\"R2 doesn't point to a const string\")\n+__failure __msg(\"R2 type=ctx expected=map_value\")\n int stream_vprintk_string_arg(void *ctx)\n {\n \tbpf_stream_vprintk(BPF_STDOUT, ctx, NULL, 0);\ndiff --git a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c\nindex f96b0c13ed1a5..12c8ac6099cae 100644\n--- a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c\n+++ b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c\n@@ -50,7 +50,7 @@ int BPF_PROG(task_kfunc_acquire_untrusted, struct task_struct *task, u64 clone_f\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"R1 is fp expected STRUCT task_struct\")\n+__failure __msg(\"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int BPF_PROG(task_kfunc_acquire_fp, struct task_struct *task, u64 clone_flags)\n {\n \tstruct task_struct *acquired, *stack_task = (struct task_struct *)\u0026clone_flags;\n@@ -179,7 +179,7 @@ int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_f\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)\n {\n \tstruct task_struct *acquired = (struct task_struct *)\u0026clone_flags;\n@@ -225,7 +225,7 @@ int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_flags)\n {\n \t/* Cannot release trusted task pointer which was not acquired. */\n@@ -333,7 +333,7 @@ int BPF_PROG(task_access_comm2, struct task_struct *task, u64 clone_flags)\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"write into memory\")\n+__failure __msg(\"only read is supported\")\n int BPF_PROG(task_access_comm3, struct task_struct *task, u64 clone_flags)\n {\n \tbpf_probe_read_kernel(task-\u003ecomm, 16, task-\u003ecomm);\n@@ -353,7 +353,7 @@ int BPF_PROG(task_access_comm4, struct task_struct *task, const char *buf, bool\n }\n \n SEC(\"tp_btf/task_newtask\")\n-__failure __msg(\"release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flags)\n {\n \tstruct task_struct *local;\ndiff --git a/tools/testing/selftests/bpf/progs/task_work_fail.c b/tools/testing/selftests/bpf/progs/task_work_fail.c\nindex 3186e7b4b24e0..bc56bdaca780b 100644\n--- a/tools/testing/selftests/bpf/progs/task_work_fail.c\n+++ b/tools/testing/selftests/bpf/progs/task_work_fail.c\n@@ -58,7 +58,7 @@ int mismatch_map(struct pt_regs *args)\n }\n \n SEC(\"perf_event\")\n-__failure __msg(\"R2 doesn't point to a map value\")\n+__failure __msg(\"R2 type=fp expected=map_value\")\n int no_map_task_work(struct pt_regs *args)\n {\n \tstruct task_struct *task;\ndiff --git a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c\nindex bf48fc43c7ab6..f7a83e5024543 100644\n--- a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c\n+++ b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c\n@@ -40,7 +40,7 @@ int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size,\n }\n \n SEC(\"?lsm.s/bpf\")\n-__failure __msg(\"R1 expected pointer to stack or const struct bpf_dynptr\")\n+__failure __msg(\"R1 type=map_value expected=fp, dynptr_ptr\")\n int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)\n {\n \tstatic struct bpf_dynptr val;\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c\nindex 7856dad3d1f38..9d42ba8244082 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_ctx.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c\n@@ -208,7 +208,7 @@ __naked void null_check_7_ctx_bind(void)\n \n SEC(\"cgroup/post_bind4\")\n __description(\"pass ctx or null check, 8: null (bind)\")\n-__failure __msg(\"R1 type=scalar expected=ctx\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void null_check_8_null_bind(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c\nindex a3d2af8dc8396..b277b1efb8c7b 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c\n@@ -177,7 +177,7 @@ __weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)\n \n SEC(\"?tp_btf/task_newtask\")\n __failure __log_level(2)\n-__msg(\"release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__msg(\"release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)\n {\n \treturn subprog_trusted_destroy(task);\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c\nindex 343fc08d97479..d1452ef6f2f9a 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c\n@@ -621,7 +621,7 @@ l0_%=:\texit;\t\t\t\t\t\t\\\n \n SEC(\"tracepoint\")\n __description(\"helper access to variable memory: size = 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)\")\n-__failure __msg(\"R1 type=scalar expected=fp\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void ptr_to_mem_or_null_8(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\n@@ -637,7 +637,7 @@ __naked void ptr_to_mem_or_null_8(void)\n \n SEC(\"tracepoint\")\n __description(\"helper access to variable memory: size \u003e 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)\")\n-__failure __msg(\"R1 type=scalar expected=fp\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void ptr_to_mem_or_null_9(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c\nindex 71cee3f583243..12786b72c6948 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c\n@@ -258,7 +258,7 @@ l0_%=:\tr0 = 0;\t\t\t\t\t\t\\\n \n SEC(\"tc\")\n __description(\"helper access to packet: test11, cls unsuitable helper 1\")\n-__failure __msg(\"helper access to the packet\")\n+__failure __msg(\"function access to the packet\")\n __naked void test11_cls_unsuitable_helper_1(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\n@@ -283,7 +283,7 @@ l0_%=:\tr0 = 0;\t\t\t\t\t\t\\\n \n SEC(\"tc\")\n __description(\"helper access to packet: test12, cls unsuitable helper 2\")\n-__failure __msg(\"helper access to the packet\")\n+__failure __msg(\"function access to the packet\")\n __naked void test12_cls_unsuitable_helper_2(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c\nnew file mode 100644\nindex 0000000000000..88009566d92f9\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c\n@@ -0,0 +1,47 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_misc.h\"\n+\n+struct nf_conn *bpf_skb_ct_lookup(struct __sk_buff *skb_ctx,\n+\t\t\t\t  struct bpf_sock_tuple *bpf_tuple,\n+\t\t\t\t  u32 tuple__sz, struct bpf_ct_opts *opts,\n+\t\t\t\t  u32 opts__sz) __ksym;\n+void bpf_ct_release(struct nf_conn *nfct) __ksym;\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+SEC(\"tc\")\n+__description(\"kfunc packet write requests writable skb\")\n+__success\n+/* bpf_unclone_prologue() */\n+__xlated(\"r6 = *(u8 *)(r1 +{{[0-9]+}})\")\n+__xlated(\"...\")\n+__xlated(\"w6 \u0026= {{(1|128)}}\")\n+__xlated(\"...\")\n+__xlated(\"if r6 == 0x0 goto\")\n+__xlated(\"r6 = r1\")\n+__xlated(\"r2 ^= r2\")\n+__xlated(\"call\")\n+__xlated(\"if r0 == 0x0 goto\")\n+__xlated(\"w0 = 2\")\n+__xlated(\"...\")\n+__xlated(\"exit\")\n+__xlated(\"r1 = r6\")\n+int kfunc_packet_write(struct __sk_buff *skb)\n+{\n+\tvoid *data_end = (void *)(long)skb-\u003edata_end;\n+\tvoid *data = (void *)(long)skb-\u003edata;\n+\tstruct bpf_sock_tuple tuple = {};\n+\tstruct nf_conn *nfct;\n+\n+\tif (data + sizeof(struct bpf_ct_opts) \u003e data_end)\n+\t\treturn 0;\n+\n+\t/* An invalid tuple size makes bpf_skb_ct_lookup() write opts-\u003eerror. */\n+\tnfct = bpf_skb_ct_lookup(skb, \u0026tuple, 1, data, sizeof(struct bpf_ct_opts));\n+\tif (nfct)\n+\t\tbpf_ct_release(nfct);\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c\nindex 401152b2b64fc..bc3dfdc1a5363 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_live_stack.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_live_stack.c\n@@ -246,7 +246,7 @@ static __used __naked void read_first_param2(void)\n SEC(\"socket\")\n __flag(BPF_F_TEST_STATE_FREQ)\n __failure\n-__msg(\"R1 type=scalar expected=map_ptr\")\n+__msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void caller_stack_pruning_callback(void)\n {\n \tasm volatile (\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 d3be69a9a7557..621248a02a1f9 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@@ -154,8 +154,7 @@ l0_%=:\tr0 = 0;\t\t\t\t\t\t\\\n \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 __msg(\"Possibly NULL pointer passed to trusted R1\")\n __failure_unpriv\n __naked void on_the_inner_map_pointer(void)\n {\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c\nindex c01abf54923d3..4b1eadddd89cd 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c\n@@ -58,7 +58,7 @@ int mapofmaps_value_as_helper_mem_buf(struct __sk_buff *skb)\n }\n \n SEC(\"?tc\")\n-__failure __msg(\"type=map_ptr_or_null expected=fp\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n int mapofmaps_value_as_helper_fixed_mem(struct __sk_buff *skb)\n {\n \tchar th[sizeof(struct tcphdr)] = {};\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c\nindex 199ad18f8eb58..799db6f5713b0 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c\n@@ -344,7 +344,7 @@ __naked void potential_reference_to_system_key(void)\n \n SEC(\"tc\")\n __description(\"reference tracking: release reference without check\")\n-__failure __msg(\"type=sock_or_null expected=sock\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void tracking_release_reference_without_check(void)\n {\n \tasm volatile (\n@@ -363,7 +363,7 @@ __naked void tracking_release_reference_without_check(void)\n \n SEC(\"tc\")\n __description(\"reference tracking: release reference to sock_common without check\")\n-__failure __msg(\"type=sock_common_or_null expected=sock\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __naked void to_sock_common_without_check(void)\n {\n \tasm volatile (\n@@ -1288,7 +1288,7 @@ l1_%=:\tr1 = r6;\t\t\t\t\t\\\n \n SEC(\"tc\")\n __description(\"reference tracking: bpf_sk_release(listen_sk)\")\n-__failure __msg(\"release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n __naked void bpf_sk_release_listen_sk(void)\n {\n \tasm volatile (\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c\nindex 4f2f3209eec81..2a136c917680f 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_sock.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_sock.c\n@@ -110,7 +110,7 @@ l0_%=:\tr0 = *(u32*)(r1 + %[bpf_sock_type]);\t\t\\\n \n SEC(\"cgroup/skb\")\n __description(\"bpf_sk_fullsock(skb-\u003esk): no !skb-\u003esk check\")\n-__failure __msg(\"type=sock_common_or_null expected=sock_common\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __failure_unpriv\n __naked void sk_no_skb_sk_check_1(void)\n {\n@@ -466,7 +466,7 @@ l1_%=:\tr0 = *(u32*)(r0 + %[bpf_sock_rx_queue_mapping__end]);\\\n \n SEC(\"cgroup/skb\")\n __description(\"bpf_tcp_sock(skb-\u003esk): no !skb-\u003esk check\")\n-__failure __msg(\"type=sock_common_or_null expected=sock_common\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R1\")\n __failure_unpriv\n __naked void sk_no_skb_sk_check_2(void)\n {\n@@ -603,7 +603,7 @@ l2_%=:\tr0 = *(u32*)(r0 + %[bpf_tcp_sock_snd_cwnd]);\t\\\n \n SEC(\"tc\")\n __description(\"bpf_sk_release(skb-\u003esk)\")\n-__failure __msg(\"release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n __naked void bpf_sk_release_skb_sk(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\n@@ -620,7 +620,7 @@ l0_%=:\tr0 = 0;\t\t\t\t\t\t\\\n \n SEC(\"tc\")\n __description(\"bpf_sk_release(bpf_sk_fullsock(skb-\u003esk))\")\n-__failure __msg(\"release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n __naked void bpf_sk_fullsock_skb_sk(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\n@@ -644,7 +644,7 @@ l1_%=:\tr1 = r0;\t\t\t\t\t\\\n \n SEC(\"tc\")\n __description(\"bpf_sk_release(bpf_tcp_sock(skb-\u003esk))\")\n-__failure __msg(\"release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1\")\n __naked void bpf_tcp_sock_skb_sk(void)\n {\n \tasm volatile (\"\t\t\t\t\t\\\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\nindex 8f0c45421f893..b5f456d57669e 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\n@@ -28,7 +28,7 @@ int BPF_PROG(get_task_exe_file_kfunc_null)\n }\n \n SEC(\"lsm.s/inode_getxattr\")\n-__failure __msg(\"R1 is fp expected STRUCT task_struct\")\n+__failure __msg(\"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int BPF_PROG(get_task_exe_file_kfunc_fp)\n {\n \tu64 x;\n@@ -80,7 +80,7 @@ int BPF_PROG(get_task_exe_file_kfunc_unreleased)\n }\n \n SEC(\"lsm.s/file_open\")\n-__failure __msg(\"release kfunc bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"release function bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1\")\n int BPF_PROG(put_file_kfunc_unacquired, struct file *file)\n {\n \t/* Can't release an unacquired pointer. */\n@@ -128,7 +128,7 @@ int BPF_PROG(path_d_path_kfunc_untrusted_from_current)\n }\n \n SEC(\"lsm.s/file_open\")\n-__failure __msg(\"kernel function bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file\")\n+__failure __msg(\"bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file\")\n int BPF_PROG(path_d_path_kfunc_type_mismatch, struct file *file)\n {\n \tbpf_path_d_path((struct path *)\u0026file-\u003ef_task_work, buf, sizeof(buf));\ndiff --git a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c\nindex d4d0f1610853a..ec4e0f3ff7920 100644\n--- a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c\n+++ b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c\n@@ -42,7 +42,7 @@ int wakeup_source_access_lock_fields(void *ctx)\n }\n \n SEC(\"syscall\")\n-__failure __msg(\"release kfunc bpf_wakeup_sources_read_unlock expects referenced PTR_TO_BTF_ID passed to R1\")\n+__failure __msg(\"R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_\")\n int wakeup_source_unlock_no_lock(void *ctx)\n {\n \tstruct bpf_ws_lock *lock = (void *)0x1;\ndiff --git a/tools/testing/selftests/bpf/progs/wq_failures.c b/tools/testing/selftests/bpf/progs/wq_failures.c\nindex 32dc8827e128b..bd30217579d4d 100644\n--- a/tools/testing/selftests/bpf/progs/wq_failures.c\n+++ b/tools/testing/selftests/bpf/progs/wq_failures.c\n@@ -48,7 +48,7 @@ __log_level(2)\n __flag(BPF_F_TEST_STATE_FREQ)\n __failure\n __msg(\": (85) call bpf_wq_init#\") /* anchor message */\n-__msg(\"pointer in R2 isn't map pointer\")\n+__msg(\"R2 type=fp expected=map_ptr\")\n long test_wq_init_nomap(void *ctx)\n {\n \tstruct bpf_wq *wq;\n@@ -98,7 +98,7 @@ __failure\n  * is a correct bpf_wq pointer.\n  */\n __msg(\": (85) call bpf_wq_set_callback#\") /* anchor message */\n-__msg(\"R1 doesn't point to a map value\")\n+__msg(\"R1 type=fp expected=map_value\")\n long test_wrong_wq_pointer(void *ctx)\n {\n \tint key = 0;\ndiff --git a/tools/testing/selftests/bpf/verifier/calls.c b/tools/testing/selftests/bpf/verifier/calls.c\nindex eb6e3baef412a..8b94b87135bcf 100644\n--- a/tools/testing/selftests/bpf/verifier/calls.c\n+++ b/tools/testing/selftests/bpf/verifier/calls.c\n@@ -31,7 +31,7 @@\n \t},\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.result = REJECT,\n-\t.errstr = \"R1 is fp expected STRUCT prog_test_fail1\",\n+\t.errstr = \"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\",\n \t.fixup_kfunc_btf_id = {\n \t\t{ \"bpf_kfunc_call_test_fail1\", 2 },\n \t},\n@@ -46,7 +46,7 @@\n \t},\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.result = REJECT,\n-\t.errstr = \"max struct nesting depth exceeded\\nR1 is fp expected STRUCT prog_test_fail2\",\n+\t.errstr = \"max struct nesting depth exceeded\\nR1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\",\n \t.fixup_kfunc_btf_id = {\n \t\t{ \"bpf_kfunc_call_test_fail2\", 2 },\n \t},\n@@ -61,7 +61,7 @@\n \t},\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.result = REJECT,\n-\t.errstr = \"R1 is fp expected STRUCT prog_test_fail3\",\n+\t.errstr = \"R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_\",\n \t.fixup_kfunc_btf_id = {\n \t\t{ \"bpf_kfunc_call_test_fail3\", 2 },\n \t},\n@@ -76,7 +76,7 @@\n \t},\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.result = REJECT,\n-\t.errstr = \"R1 expected pointer to ctx, but got fp\",\n+\t.errstr = \"R1 type=fp expected=ctx\",\n \t.fixup_kfunc_btf_id = {\n \t\t{ \"bpf_kfunc_call_test_pass_ctx\", 2 },\n \t},\n@@ -152,7 +152,7 @@\n \t},\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.result = REJECT,\n-\t.errstr = \"kernel function bpf_kfunc_call_memb1_release R1 expected pointer\",\n+\t.errstr = \"bpf_kfunc_call_memb1_release R1 expected pointer\",\n \t.fixup_kfunc_btf_id = {\n \t\t{ \"bpf_kfunc_call_memb_acquire\", 1 },\n \t\t{ \"bpf_kfunc_call_memb1_release\", 5 },\ndiff --git a/tools/testing/selftests/bpf/verifier/map_kptr.c b/tools/testing/selftests/bpf/verifier/map_kptr.c\nindex 4b39f8472f9b5..345cecc722a38 100644\n--- a/tools/testing/selftests/bpf/verifier/map_kptr.c\n+++ b/tools/testing/selftests/bpf/verifier/map_kptr.c\n@@ -311,7 +311,7 @@\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.fixup_map_kptr = { 1 },\n \t.result = REJECT,\n-\t.errstr = \"R1 type=rcu_ptr_or_null_ expected=percpu_ptr_\",\n+\t.errstr = \"Possibly NULL pointer passed to trusted R1\",\n },\n {\n \t\"map_kptr: ref: reject off != 0\",\n@@ -342,7 +342,7 @@\n \t.prog_type = BPF_PROG_TYPE_SCHED_CLS,\n \t.fixup_map_kptr = { 1 },\n \t.result = REJECT,\n-\t.errstr = \"invalid kptr access, R2 type=ptr_prog_test_ref_kfunc expected=ptr_prog_test_member\",\n+\t.errstr = \"R2 must have zero offset when passed to release func\",\n },\n {\n \t\"map_kptr: ref: reference state created and released on xchg\",\n"
}

2/1 2026/09/11 22:36 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "check_func_args",
    "check_func_arg",
    "check_helper_call",
    "check_kfunc_call",
    "bpf_do_misc_fixups",
    "btf_check_func_arg_match",
    "process_arg_ptr_to_btf_id"
  ],
  "Reasoning": "The patch modifies core BPF verifier logic, unifying argument verification for BPF helpers and kfuncs into check_func_args() and check_func_arg(). It refactors register type checking, nullability and release checks, packet data access restrictions, and instruction fixups in bpf_do_misc_fixups(). This reachable logic in kernel/bpf/ directly impacts program verification and execution during BPF_PROG_LOAD syscalls, warranting fuzzing for potential safety regressions, verifier bugs, or assertion failures.",
  "WorthFuzzing": true
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 2be0c3fa03e0a3b9e475d93a3ffc7b797c2ed327
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 11 22:36:09 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index e80963971f680..d0066d744ceb9 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -874,7 +874,7 @@ enum bpf_type_flag {
 
 /* function argument constraints */
 enum bpf_arg_type {
-	ARG_DONTCARE = 0,	/* unused argument in helper function */
+	ARG_UNUSED = 0,		/* unused argument; terminates argument iteration */
 
 	/* the following constraints used to prototype
 	 * bpf_map_lookup/update/delete_elem() functions
@@ -909,6 +909,22 @@ enum bpf_arg_type {
 	ARG_PTR_TO_TIMER,	/* pointer to bpf_timer */
 	ARG_KPTR_XCHG_DEST,	/* pointer to destination that kptrs are bpf_kptr_xchg'd into */
 	ARG_PTR_TO_DYNPTR,      /* pointer to bpf_dynptr. See bpf_type_flag for dynptr type */
+
+	ARG_CONST_SCALAR,	/* scalar known at verification time */
+	ARG_CONST_MEM_SIZE,	/* ARG_MEM_SIZE that must be constant */
+	ARG_PTR_TO_ALLOC_BTF_ID,	/* pointer to an allocated object */
+	ARG_PTR_TO_REFCOUNTED_KPTR,	/* pointer to a refcounted local kptr */
+	ARG_PTR_TO_ITER,	/* pointer to an iterator */
+	ARG_PTR_TO_LIST_HEAD,	/* pointer to bpf_list_head */
+	ARG_PTR_TO_LIST_NODE,	/* pointer to bpf_list_node */
+	ARG_PTR_TO_RB_ROOT,	/* pointer to bpf_rb_root */
+	ARG_PTR_TO_RB_NODE,	/* pointer to bpf_rb_node */
+	ARG_PTR_TO_WORKQUEUE,	/* pointer to bpf_wq */
+	ARG_PTR_TO_TASK_WORK,	/* pointer to bpf_task_work */
+	ARG_PTR_TO_IRQ_FLAG,	/* pointer to saved IRQ flags on the stack */
+	ARG_PTR_TO_RES_SPIN_LOCK,	/* pointer to bpf_res_spin_lock */
+	ARG_PTR_TO_PROG_AUX,	/* pointer to the caller's bpf_prog_aux */
+	ARG_IGNORE,		/* argument the verifier does not check at all */
 	__BPF_ARG_TYPE_MAX,
 
 	/* Extended arg_types. */
@@ -1005,13 +1021,13 @@ struct bpf_func_proto {
 	};
 	union {
 		struct {
-			u32 *arg1_btf_id;
-			u32 *arg2_btf_id;
-			u32 *arg3_btf_id;
-			u32 *arg4_btf_id;
-			u32 *arg5_btf_id;
+			const u32 *arg1_btf_id;
+			const u32 *arg2_btf_id;
+			const u32 *arg3_btf_id;
+			const u32 *arg4_btf_id;
+			const u32 *arg5_btf_id;
 		};
-		u32 *arg_btf_id[MAX_BPF_FUNC_ARGS];
+		const u32 *arg_btf_id[MAX_BPF_FUNC_ARGS];
 		struct {
 			size_t arg1_size;
 			size_t arg2_size;
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 9727df5af83ab..1e7593e8d5c5d 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1591,7 +1591,7 @@ struct bpf_call_arg_meta {
 	 * verification logic
 	 *   bpf_obj_drop/bpf_percpu_obj_drop
 	 *     Record the local kptr type to be drop'd
-	 *   bpf_refcount_acquire (via KF_ARG_PTR_TO_REFCOUNTED_KPTR arg type)
+	 *   bpf_refcount_acquire (via ARG_PTR_TO_REFCOUNTED_KPTR arg type)
 	 *     Record the local kptr type to be refcount_incr'd and use
 	 *     arg_owning_ref to determine whether refcount_acquire should be
 	 *     fallible
@@ -1599,7 +1599,6 @@ struct bpf_call_arg_meta {
 	struct btf *arg_btf;
 	u32 arg_btf_id;
 	bool arg_owning_ref;
-	bool arg_prog;
 
 	struct {
 		struct btf_field *field;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 31057c8f3a7c2..122a4101ce944 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8244,7 +8244,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 			return -EINVAL;
 		}
 		if (btf_type_is_int(t) || btf_is_any_enum(t)) {
-			sub->args[i].arg_type = ARG_ANYTHING;
+			sub->args[i].arg_type = ARG_SCALAR;
 			continue;
 		}
 		if (!is_global)
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 0abbbe177e317..a2cac59c66391 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -960,6 +960,35 @@ const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_t
 	}
 }
 
+const char *bpf_diag_arg_type_plain(enum bpf_arg_type type)
+{
+	switch (base_type(type)) {
+	case ARG_MEM_SIZE:
+	case ARG_CONST_MEM_SIZE:
+		return "an integer scalar length for this memory argument";
+	case ARG_PTR_TO_CTX:
+		return "the original program context pointer or preserve it before modifying registers";
+	case ARG_SCALAR:
+	case ARG_CONST_SCALAR:
+	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		return "an integer scalar value for this argument, not a pointer or resource object";
+	case ARG_PTR_TO_CONST_STR:
+		return "a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value";
+	case ARG_PTR_TO_DYNPTR:
+		return "the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path";
+	case ARG_PTR_TO_ALLOC_BTF_ID:
+		return "a pointer returned by the matching BPF object allocation path";
+	case ARG_PTR_TO_REFCOUNTED_KPTR:
+		return "an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field";
+	case ARG_PTR_TO_ITER:
+		return "the address of a stack iterator object for iterator new, next, and destroy calls";
+	case ARG_PTR_TO_IRQ_FLAG:
+		return "the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave()";
+	default:
+		return "a value with one of the accepted pointer or scalar types for this call";
+	}
+}
+
 static const char *diag_arg_ordinal(int argno)
 {
 	switch (argno) {
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index d1b79945008a8..a4102fb049ece 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -51,6 +51,7 @@ const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list
 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);
+const char *bpf_diag_arg_type_plain(enum bpf_arg_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);
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index fcf68cfb91e91..2add8001c3ec3 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -2020,7 +2020,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			goto next_insn;
 		}
 
-		if (insn->imm == BPF_FUNC_timer_set_callback) {
+		aux = &env->insn_aux_data[i + delta];
+		if (aux->arg_prog) {
 			/* The verifier will process callback_fn as many times as necessary
 			 * with different maps and the register states prepared by
 			 * set_timer_callback_state will be accurate.
@@ -2035,7 +2036,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			 *     bpf_timer_set_callback-ed will return -EINVAL.
 			 */
 			struct bpf_insn ld_addrs[2] = {
-				BPF_LD_IMM64(BPF_REG_3, (long)prog->aux),
+				BPF_LD_IMM64(aux->arg_prog, (long)prog->aux),
 			};
 
 			insn_buf[0] = ld_addrs[0];
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..051b6654e57c6 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1510,6 +1510,7 @@ static const struct bpf_func_proto bpf_timer_set_callback_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_TIMER,
 	.arg2_type	= ARG_PTR_TO_FUNC,
+	.arg3_type	= ARG_PTR_TO_PROG_AUX,
 };
 
 static bool defer_timer_wq_op(void)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e79750e24808..617a277c3558c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -960,8 +960,12 @@ static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct
 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)
+	/*
+	 * ARG_PTR_TO_DYNPTR without a type flag takes any type of dynptr.
+	 * Test the flags rather than the whole arg_type, which may carry
+	 * unrelated ones such as PTR_MAYBE_NULL.
+	 */
+	if (!(arg_type & DYNPTR_TYPE_FLAG_MASK))
 		return true;
 
 	return dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);
@@ -4877,7 +4881,7 @@ static int check_map_access(struct bpf_verifier_env *env, struct bpf_reg_state *
 }
 
 static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
-			       const struct bpf_func_proto *fn,
+			       const struct bpf_call_arg_meta *meta,
 			       enum bpf_access_type t)
 {
 	enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
@@ -4901,10 +4905,11 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
 	case BPF_PROG_TYPE_LWT_XMIT:
 	case BPF_PROG_TYPE_SK_SKB:
 	case BPF_PROG_TYPE_SK_MSG:
-		if (fn)
-			return fn->pkt_access;
+		if (meta && !meta->btf && meta->func_id)
+			return meta->fn->pkt_access;
 
-		env->seen_direct_write = true;
+		if (t == BPF_WRITE)
+			env->seen_direct_write = true;
 		return true;
 
 	case BPF_PROG_TYPE_CGROUP_SOCKOPT:
@@ -5162,18 +5167,6 @@ static u32 *reg2btf_ids[__BPF_REG_TYPE_MAX] = {
 	[CONST_PTR_TO_MAP] = btf_bpf_map_id,
 };
 
-static enum bpf_reg_type lookup_reg2btf_ids(u32 ref_id)
-{
-	enum bpf_reg_type type;
-
-	for (type = 0; type < __BPF_REG_TYPE_MAX; type++) {
-		if (reg2btf_ids[type] && *reg2btf_ids[type] == ref_id)
-			return type;
-	}
-
-	return NOT_INIT;
-}
-
 static bool is_trusted_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)
 {
 	/* A referenced register is always trusted. */
@@ -7103,6 +7096,10 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
 	switch (base_type(reg->type)) {
 	case PTR_TO_PACKET:
 	case PTR_TO_PACKET_META:
+		if (!may_access_direct_pkt_data(env, meta, access_type)) {
+			verbose(env, "function access to the packet is not allowed\n");
+			return -EACCES;
+		}
 		return check_packet_access(env, reg, argno, 0, access_size,
 					   zero_size_allowed);
 	case PTR_TO_MAP_KEY:
@@ -7219,7 +7216,7 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
 	 * the memory that the helper could just partially fill up.
 	 */
 	if (!tnum_is_const(size_reg->var_off))
-		meta = NULL;
+		meta->arg_raw_mem.regno = 0;
 
 	if (reg_smin(size_reg) < 0) {
 		verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
@@ -7663,11 +7660,11 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
 /*
  * Validate dynptr arguments for helper, kfunc and subprog.
  *
- * @dynptr is both input and output. It is populated when the argument is
- * tagged with MEM_UNINIT (i.e., the dynptr argument that will be constructed)
- * and consumed when the argument is expecting to be an initialized dynptr.
- * @parent_id is used to track the referenced parent object (e.g., file or skb in
- * qdisc program) when constructing a dynptr.
+ * @meta carries the dynptr and referenced-object state. The dynptr is populated
+ * when the argument is tagged with MEM_UNINIT (i.e., the dynptr argument that
+ * will be constructed) and consumed when the argument is expected to be an
+ * initialized dynptr. The reference tracks the parent object (e.g., file or skb
+ * in qdisc program) when constructing a dynptr.
  *
  * There are two register types representing a bpf_dynptr, one is PTR_TO_STACK
  * which points to a stack slot, and the other is CONST_PTR_TO_DYNPTR.
@@ -7684,9 +7681,8 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
  * 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, const char *call_name,
-			       enum bpf_arg_type arg_type,
-			       struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)
+			       argno_t argno, int insn_idx, enum bpf_arg_type arg_type,
+			       struct bpf_call_arg_meta *meta)
 {
 	int spi, err = 0;
 
@@ -7695,7 +7691,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			"%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,
+			env, insn_idx, argno, meta->func_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));
@@ -7723,7 +7719,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			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.",
+				"This function 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;
 		}
@@ -7736,7 +7732,8 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 				return err;
 		}
 
-		err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx, ref_obj, dynptr);
+		err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx,
+					      &meta->ref_obj, &meta->dynptr);
 	} else /* OBJ_RELEASE and None case from above */ {
 		/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
 		if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
@@ -7766,7 +7763,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			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,
+				env, insn_idx, argno, meta->func_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));
@@ -7785,11 +7782,9 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			reg = &state->stack[spi].spilled_ptr;
 		}
 
-		if (dynptr) {
-			dynptr->type = reg->dynptr.type;
-			dynptr->id = reg->id;
-			dynptr->parent_id = reg->parent_id;
-		}
+		meta->dynptr.type = reg->dynptr.type;
+		meta->dynptr.id = reg->id;
+		meta->dynptr.parent_id = reg->parent_id;
 	}
 	return err;
 }
@@ -7853,8 +7848,8 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 			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.");
+			"the function 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 function.");
 		return -EINVAL;
 	}
 	t = btf_type_by_id(meta->btf, btf_id);
@@ -8178,9 +8173,43 @@ static bool arg_type_is_dynptr(enum bpf_arg_type type)
 	return base_type(type) == ARG_PTR_TO_DYNPTR;
 }
 
+/*
+ * An argument that only ever takes a scalar, so a zero register passed to it
+ * is a value rather than a NULL pointer.
+ */
+static bool arg_type_is_scalar(enum bpf_arg_type type)
+{
+	switch (base_type(type)) {
+	case ARG_SCALAR:
+	case ARG_CONST_SCALAR:
+	case ARG_MEM_SIZE:
+	case ARG_MEM_SIZE_OR_ZERO:
+	case ARG_CONST_MEM_SIZE:
+	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		return true;
+	default:
+		return false;
+	}
+}
+
+/*
+ * A kfunc is named by a BTF ID, which can take the same numeric value as an
+ * enum bpf_func_id. Only test meta->func_id against a BPF_FUNC_* once the call
+ * is known to be to a helper; meta->btf is set only for a kfunc.
+ */
+static bool is_helper_call(const struct bpf_call_arg_meta *meta, enum bpf_func_id func_id)
+{
+	return !meta->btf && meta->func_id == func_id;
+}
+
+static bool is_kfunc_call(const struct bpf_call_arg_meta *meta, u32 btf_id)
+{
+	return meta->btf && meta->func_id == btf_id;
+}
+
 static int resolve_map_arg_type(struct bpf_verifier_env *env,
-				 const struct bpf_call_arg_meta *meta,
-				 enum bpf_arg_type *arg_type)
+				const struct bpf_call_arg_meta *meta,
+				enum bpf_arg_type *arg_type)
 {
 	if (!meta->map.ptr) {
 		/* kernel subsystem misconfigured verifier */
@@ -8199,7 +8228,7 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,
 		}
 		break;
 	case BPF_MAP_TYPE_BLOOM_FILTER:
-		if (meta->func_id == BPF_FUNC_map_peek_elem)
+		if (is_helper_call(meta, BPF_FUNC_map_peek_elem))
 			*arg_type = ARG_PTR_TO_MAP_VALUE;
 		break;
 	default:
@@ -8208,6 +8237,48 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,
 	return 0;
 }
 
+static int resolve_func_arg_type(struct bpf_verifier_env *env,
+				 struct bpf_reg_state *reg, u32 arg,
+				 struct bpf_call_arg_meta *meta,
+				 enum bpf_arg_type *arg_type, u32 *arg_size);
+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				     argno_t argno, enum bpf_arg_type arg_type,
+				     const struct btf *arg_btf, u32 arg_btf_id,
+				     struct bpf_call_arg_meta *meta, int insn_idx);
+static bool is_kfunc_arg_nonown_allowed(const struct btf *btf,
+					const struct btf_param *arg);
+static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
+					  const struct btf_param *arg,
+					  const char *name);
+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_obj_drop_kfunc(u32 func_id);
+static bool is_bpf_percpu_obj_drop_kfunc(u32 func_id);
+static bool is_bpf_rbtree_add_kfunc(u32 func_id);
+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta);
+static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env);
+static int process_irq_flag(struct bpf_verifier_env *env,
+			    struct bpf_reg_state *reg, argno_t argno,
+			    struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_list_head(struct bpf_verifier_env *env,
+					   struct bpf_reg_state *reg,
+					   argno_t argno,
+					   struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_rbtree_root(struct bpf_verifier_env *env,
+					     struct bpf_reg_state *reg,
+					     argno_t argno,
+					     struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_list_node(struct bpf_verifier_env *env,
+					   struct bpf_reg_state *reg,
+					   argno_t argno,
+					   struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,
+					     struct bpf_reg_state *reg,
+					     argno_t argno,
+					     struct bpf_call_arg_meta *meta);
+static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env);
+
 struct bpf_reg_types {
 	const enum bpf_reg_type types[10];
 	u32 *btf_id;
@@ -8251,7 +8322,7 @@ static const struct bpf_reg_types mem_types = {
 	},
 };
 
-static const struct bpf_reg_types spin_lock_types = {
+static const struct bpf_reg_types map_value_or_alloc_obj_types = {
 	.types = {
 		PTR_TO_MAP_VALUE,
 		PTR_TO_BTF_ID | MEM_ALLOC,
@@ -8280,7 +8351,30 @@ static const struct bpf_reg_types percpu_btf_ptr_types = {
 static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
 static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
 static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
-static const struct bpf_reg_types timer_types = { .types = { PTR_TO_MAP_VALUE } };
+static const struct bpf_reg_types map_value_types = { .types = { PTR_TO_MAP_VALUE } };
+static const struct bpf_reg_types arena_types = {
+	.types = {
+		PTR_TO_ARENA,
+		SCALAR_VALUE,
+	}
+};
+
+static const struct bpf_reg_types alloc_obj_drop_types = {
+	.types = {
+		PTR_TO_BTF_ID | MEM_ALLOC,
+		PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU,
+	}
+};
+
+static const struct bpf_reg_types alloc_obj_types = {
+	.types = {
+		PTR_TO_BTF_ID | MEM_ALLOC,
+		PTR_TO_BTF_ID | MEM_ALLOC | MEM_RCU,
+		PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF,
+		PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU,
+	}
+};
+
 static const struct bpf_reg_types kptr_xchg_dest_types = {
 	.types = {
 		PTR_TO_MAP_VALUE,
@@ -8311,16 +8405,30 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 #endif
 	[ARG_PTR_TO_SOCKET]		= &fullsock_types,
 	[ARG_PTR_TO_BTF_ID]		= &btf_ptr_types,
-	[ARG_PTR_TO_SPIN_LOCK]		= &spin_lock_types,
+	[ARG_PTR_TO_SPIN_LOCK]		= &map_value_or_alloc_obj_types,
 	[ARG_PTR_TO_MEM]		= &mem_types,
 	[ARG_PTR_TO_RINGBUF_MEM]	= &ringbuf_mem_types,
 	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
 	[ARG_PTR_TO_FUNC]		= &func_ptr_types,
 	[ARG_PTR_TO_STACK]		= &stack_ptr_types,
 	[ARG_PTR_TO_CONST_STR]		= &const_str_ptr_types,
-	[ARG_PTR_TO_TIMER]		= &timer_types,
+	[ARG_PTR_TO_TIMER]		= &map_value_types,
 	[ARG_KPTR_XCHG_DEST]		= &kptr_xchg_dest_types,
 	[ARG_PTR_TO_DYNPTR]		= &dynptr_types,
+	[ARG_CONST_SCALAR]		= &scalar_types,
+	[ARG_CONST_MEM_SIZE]		= &scalar_types,
+	[ARG_PTR_TO_ALLOC_BTF_ID]	= &alloc_obj_drop_types,
+	[ARG_PTR_TO_REFCOUNTED_KPTR]	= &alloc_obj_types,
+	[ARG_PTR_TO_ITER]		= &stack_ptr_types,
+	[ARG_PTR_TO_LIST_HEAD]		= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_LIST_NODE]		= &alloc_obj_types,
+	[ARG_PTR_TO_RB_ROOT]		= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_RB_NODE]		= &alloc_obj_types,
+	[ARG_PTR_TO_RES_SPIN_LOCK]	= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_WORKQUEUE]		= &map_value_types,
+	[ARG_PTR_TO_TASK_WORK]		= &map_value_types,
+	[ARG_PTR_TO_IRQ_FLAG]		= &stack_ptr_types,
+	[ARG_PTR_TO_ARENA]		= &arena_types,
 };
 
 static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
@@ -8360,6 +8468,71 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
 	bpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);
 }
 
+static int check_func_arg_nullability(struct bpf_verifier_env *env,
+				      struct bpf_reg_state *reg, argno_t argno,
+				      enum bpf_arg_type arg_type,
+				      struct bpf_call_arg_meta *meta, int insn_idx)
+{
+	const char *expected_type = "pointer";
+
+	if (arg_type_is_scalar(arg_type) || type_may_be_null(arg_type) ||
+	    (!bpf_register_is_null(reg) && !type_may_be_null(reg->type)))
+		return 0;
+
+	if (meta->btf) {
+		u32 arg_btf_id;
+
+		arg_btf_id = btf_params(meta->func_proto)[arg_idx_from_argno(argno)].type;
+		expected_type = bpf_diag_fmt(env, "value of type %s",
+					     bpf_diag_fmt_btf_type(env, meta->btf, arg_btf_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, meta->func_name,
+			      "Add a NULL check and make the call only on the non-NULL path.",
+			      "the pointer may be NULL, but this call requires a non-NULL %s",
+			      expected_type);
+	return -EACCES;
+}
+
+static int check_func_arg_release(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				  argno_t argno, enum bpf_arg_type arg_type,
+				  struct bpf_call_arg_meta *meta, int insn_idx)
+{
+	const char *expected_type = "pointer";
+
+	if (!arg_type_is_release(arg_type))
+		return 0;
+
+	if (arg_type_is_dynptr(arg_type) || reg_is_referenced(env, reg) ||
+	    bpf_register_is_null(reg))
+		return 0;
+
+	verbose(env, "release function %s expects referenced PTR_TO_BTF_ID passed to %s\n",
+		meta->func_name, reg_arg_name(env, argno));
+
+	if (meta->btf) {
+		const struct btf_param *btf_arg;
+		const struct btf_type *t;
+		u32 ref_id;
+
+		btf_arg = &btf_params(meta->func_proto)[arg_idx_from_argno(argno)];
+		ref_id = btf_arg->type;
+		t = btf_type_skip_modifiers(meta->btf, btf_arg->type, NULL);
+		if (btf_type_is_ptr(t))
+			btf_type_skip_modifiers(meta->btf, t->type, &ref_id);
+		expected_type = bpf_diag_fmt(env, "value of type %s",
+					     bpf_diag_fmt_btf_type(env, meta->btf, ref_id));
+	}
+
+	bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+			      bpf_diag_fmt(env, "Pass the resource-owning %s returned by the matching acquire call, or avoid the release function after ownership has already been transferred or released.",
+					   expected_type),
+			      "release functions require a value that owns a live resource returned by a matching acquire function");
+	return -EINVAL;
+}
+
 static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
 					       const enum bpf_reg_type *types, int count)
 {
@@ -8381,19 +8554,21 @@ static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
 }
 
 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, const char *call_name)
+			  enum bpf_arg_type arg_type, struct bpf_call_arg_meta *meta)
 {
 	enum bpf_reg_type expected, type = reg->type;
 	const struct bpf_reg_types *compatible;
 	const char *actual, *accepted;
-	int i, j, err;
+	int i, j;
 
 	compatible = compatible_reg_types[base_type(arg_type)];
 	if (!compatible) {
 		verifier_bug(env, "unsupported arg type %d", arg_type);
 		return -EFAULT;
 	}
+	if (meta->btf && base_type(arg_type) == ARG_PTR_TO_BTF_ID &&
+	    (base_type(type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(type)]))
+		goto found;
 
 	/* ARG_PTR_TO_MEM + RDONLY is compatible with PTR_TO_MEM and PTR_TO_MEM + RDONLY,
 	 * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM and NOT with PTR_TO_MEM + RDONLY
@@ -8413,9 +8588,14 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 		type &= ~PTR_MAYBE_NULL;
 	if (base_type(arg_type) == ARG_PTR_TO_MEM)
 		type &= ~DYNPTR_TYPE_FLAG_MASK;
+	/* Allow allocated memory for kfunc ARG_PTR_TO_MEM but not helper. */
+	if (meta->btf && base_type(arg_type) == ARG_PTR_TO_MEM &&
+	    type_is_ptr_alloc_obj(type))
+		type = PTR_TO_MEM;
 
 	/* Local kptr types are allowed as the source argument of bpf_kptr_xchg */
-	if (meta->func_id == BPF_FUNC_kptr_xchg && type_is_alloc(type) && reg_from_argno(argno) == BPF_REG_2) {
+	if (is_helper_call(meta, BPF_FUNC_kptr_xchg) && type_is_alloc(type) &&
+	    reg_from_argno(argno) == BPF_REG_2) {
 		type &= ~MEM_ALLOC;
 		type &= ~MEM_PERCPU;
 	}
@@ -8435,115 +8615,13 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 	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.",
+	bpf_diag_call_arg_fmt(env, env->insn_idx, argno, meta->func_name,
+			      bpf_diag_fmt(env, "Pass %s.", bpf_diag_arg_type_plain(arg_type)),
 			      "it has type %s, but this argument accepts %s",
 			      actual, accepted);
 	return -EACCES;
 
 found:
-	if (base_type(reg->type) != PTR_TO_BTF_ID)
-		return 0;
-
-	if (compatible == &mem_types) {
-		if (!(arg_type & MEM_RDONLY)) {
-			verbose(env,
-				"%s() may write into memory pointed by %s type=%s\n",
-				func_id_name(meta->func_id),
-				reg_arg_name(env, argno), reg_type_str(env, reg->type));
-			return -EACCES;
-		}
-		return 0;
-	}
-
-	switch ((int)reg->type) {
-	case PTR_TO_BTF_ID:
-	case PTR_TO_BTF_ID | PTR_TRUSTED:
-	case PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL:
-	case PTR_TO_BTF_ID | MEM_RCU:
-	case PTR_TO_BTF_ID | PTR_MAYBE_NULL:
-	case PTR_TO_BTF_ID | PTR_MAYBE_NULL | MEM_RCU:
-	{
-		/* For bpf_sk_release, it needs to match against first member
-		 * 'struct sock_common', hence make an exception for it. This
-		 * allows bpf_sk_release to work for multiple socket types.
-		 */
-		bool strict_type_match = arg_type_is_release(arg_type) &&
-					 meta->func_id != BPF_FUNC_sk_release;
-
-		if (type_may_be_null(reg->type) &&
-		    (!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;
-		}
-
-		if (!arg_btf_id) {
-			if (!compatible->btf_id) {
-				verifier_bug(env, "missing arg compatible BTF ID");
-				return -EFAULT;
-			}
-			arg_btf_id = compatible->btf_id;
-		}
-
-		if (meta->func_id == BPF_FUNC_kptr_xchg) {
-			if (map_kptr_match_type(env, meta->kptr_field, reg, reg_from_argno(argno)))
-				return -EACCES;
-		} else {
-			if (arg_btf_id == BPF_PTR_POISON) {
-				verbose(env, "verifier internal error:");
-				verbose(env, "%s has non-overwritten BPF_PTR_POISON type\n",
-					reg_arg_name(env, argno));
-				return -EACCES;
-			}
-
-			err = __check_ptr_off_reg(env, reg, argno, true);
-			if (err)
-				return err;
-
-			if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id,
-						  reg->var_off.value, btf_vmlinux, *arg_btf_id,
-						  strict_type_match, !type_is_alloc(reg->type))) {
-				verbose(env, "%s is of type %s but %s is expected\n",
-					reg_arg_name(env, argno),
-					btf_type_name(reg->btf, reg->btf_id),
-					btf_type_name(btf_vmlinux, *arg_btf_id));
-				return -EACCES;
-			}
-		}
-		break;
-	}
-	case PTR_TO_BTF_ID | MEM_ALLOC:
-	case PTR_TO_BTF_ID | MEM_PERCPU | MEM_ALLOC:
-	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:
-	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
-		if (meta->func_id != BPF_FUNC_spin_lock && meta->func_id != BPF_FUNC_spin_unlock &&
-		    meta->func_id != BPF_FUNC_kptr_xchg) {
-			verifier_bug(env, "unimplemented handling of MEM_ALLOC");
-			return -EFAULT;
-		}
-		/* Check if local kptr in src arg matches kptr in dst arg */
-		if (meta->func_id == BPF_FUNC_kptr_xchg) {
-			int regno = reg_from_argno(argno);
-
-			if (regno == BPF_REG_2 &&
-			    map_kptr_match_type(env, meta->kptr_field, reg, regno))
-				return -EACCES;
-		}
-		break;
-	case PTR_TO_BTF_ID | MEM_PERCPU:
-	case PTR_TO_BTF_ID | MEM_PERCPU | MEM_RCU:
-	case PTR_TO_BTF_ID | MEM_PERCPU | PTR_TRUSTED:
-		/* Handled by helper specific checks */
-		break;
-	default:
-		verifier_bug(env, "invalid PTR_TO_BTF_ID register for type match");
-		return -EFAULT;
-	}
 	return 0;
 }
 
@@ -8564,10 +8642,9 @@ reg_find_field_offset(const struct bpf_reg_state *reg, s32 off, u32 fields)
 	return field;
 }
 
-static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
-				    const struct bpf_reg_state *reg, argno_t argno,
-				    enum bpf_arg_type arg_type,
-				    bool btf_id_fixed_off_ok)
+static int check_func_arg_reg_off(struct bpf_verifier_env *env,
+				  const struct bpf_reg_state *reg, argno_t argno,
+				  enum bpf_arg_type arg_type)
 {
 	u32 type = reg->type;
 
@@ -8623,12 +8700,15 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
 		/* When referenced PTR_TO_BTF_ID is passed to release function,
-		 * its fixed offset must be 0. In the other cases, fixed offset
-		 * can be non-zero unless the caller requires otherwise.
-		 * var_off always must be 0 for PTR_TO_BTF_ID, hence we still
-		 * need to do checks instead of returning.
+		 * its fixed offset must be 0. bpf_refcount_acquire() returns the
+		 * pointer it was given while incrementing the refcount at the
+		 * refcount field offset, so it needs a zero offset too. In the
+		 * other cases, fixed offset can be non-zero. var_off always must
+		 * be 0 for PTR_TO_BTF_ID, hence we still need to do checks
+		 * instead of returning.
 		 */
-		return __check_ptr_off_reg(env, reg, argno, btf_id_fixed_off_ok);
+		return __check_ptr_off_reg(env, reg, argno,
+					   base_type(arg_type) != ARG_PTR_TO_REFCOUNTED_KPTR);
 	case PTR_TO_CTX:
 		/*
 		 * Allow fixed and variable offsets for syscall context, but
@@ -8636,7 +8716,7 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 		 * otherwise we may get modified ctx in tail called programs and
 		 * global subprogs (that may act as extension prog hooks).
 		 */
-		if (arg_type != ARG_PTR_TO_CTX && is_var_ctx_off_allowed(env->prog))
+		if (base_type(arg_type) != ARG_PTR_TO_CTX && is_var_ctx_off_allowed(env->prog))
 			return 0;
 		fallthrough;
 	default:
@@ -8644,13 +8724,6 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 	}
 }
 
-static int check_func_arg_reg_off(struct bpf_verifier_env *env,
-				  const struct bpf_reg_state *reg, argno_t argno,
-				  enum bpf_arg_type arg_type)
-{
-	return __check_func_arg_reg_off(env, reg, argno, arg_type, true);
-}
-
 static int check_arg_const_str(struct bpf_verifier_env *env,
 			       struct bpf_reg_state *reg, argno_t argno)
 {
@@ -8816,61 +8889,58 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			  struct bpf_call_arg_meta *meta,
 			  int insn_idx)
 {
+	const struct btf_param *btf_arg = meta->btf ? &btf_params(meta->func_proto)[arg] : NULL;
 	const struct bpf_func_proto *fn = meta->fn;
-	u32 regno = BPF_REG_1 + arg;
-	struct bpf_reg_state *reg = reg_state(env, regno);
+	struct bpf_func_state *caller = cur_func(env);
+	struct bpf_reg_state *regs = cur_regs(env);
+	argno_t argno = argno_from_arg(arg + 1);
+	struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, arg);
 	enum bpf_arg_type arg_type = fn->arg_type[arg];
-	argno_t argno = argno_from_reg(regno);
-	enum bpf_reg_type type = reg->type;
-	u32 *arg_btf_id = NULL;
+	int regno = reg_from_argno(argno);
+	u32 arg_size = arg_type & MEM_FIXED_SIZE ? fn->arg_size[arg] : 0;
 	u32 key_size;
 	int err = 0;
 
-	if (arg_type == ARG_DONTCARE)
+	if (arg_type == ARG_PTR_TO_PROG_AUX) {
+		cur_aux(env)->arg_prog = regno;
 		return 0;
+	}
 
-	err = check_reg_arg(env, regno, SRC_OP);
-	if (err)
-		return err;
+	if (arg_type == ARG_IGNORE)
+		return 0;
+
+	if (regno >= 0) {
+		err = check_reg_arg(env, regno, SRC_OP);
+		if (err)
+			return err;
+	}
 
+	/* Preserve the legacy helper behavior for privileged pointer leaks. */
 	if (arg_type == ARG_ANYTHING) {
-		if (is_pointer_value(env, regno)) {
-			verbose(env, "R%d leaks addr into helper function\n",
-				regno);
+		if (__is_pointer_value(env->allow_ptr_leaks, reg)) {
+			verbose(env, "%s leaks addr into helper function\n",
+				reg_arg_name(env, argno));
 			return -EACCES;
 		}
 		return 0;
 	}
 
-	if (type_is_pkt_pointer(type) &&
-	    !may_access_direct_pkt_data(env, fn, BPF_READ)) {
-		verbose(env, "helper access to the packet is not allowed\n");
-		return -EACCES;
-	}
-
-	if (base_type(arg_type) == ARG_PTR_TO_MAP_VALUE) {
-		err = resolve_map_arg_type(env, meta, &arg_type);
-		if (err)
-			return err;
-	}
+	err = resolve_func_arg_type(env, reg, arg, meta, &arg_type, &arg_size);
+	if (err)
+		return err;
 
 	if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
-		/* A NULL register has a SCALAR_VALUE type, so skip
-		 * type checking.
-		 */
-		err = mark_chain_precision(env, regno);
+		err = mark_arg_precision(env, argno);
 		if (err)
 			return err;
-		goto skip_type_check;
+		return 0;
 	}
 
-	/* arg_btf_id and arg_size are in a union. */
-	if (base_type(arg_type) == ARG_PTR_TO_BTF_ID ||
-	    base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
-		arg_btf_id = fn->arg_btf_id[arg];
+	err = check_func_arg_nullability(env, reg, argno, arg_type, meta, insn_idx);
+	if (err)
+		return err;
 
-	err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,
-			     func_id_name(meta->func_id));
+	err = check_reg_type(env, reg, argno, arg_type, meta);
 	if (err)
 		return err;
 
@@ -8878,22 +8948,27 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	if (err)
 		return err;
 
-skip_type_check:
-	if (arg_type_is_release(arg_type) && !arg_type_is_dynptr(arg_type) &&
-	    !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;
-	}
+	err = check_func_arg_release(env, reg, argno, arg_type, meta, insn_idx);
+	if (err)
+		return err;
 
 	if (reg_is_referenced(env, reg))
 		update_ref_obj(&meta->ref_obj, reg);
 
 	switch (base_type(arg_type)) {
+	case ARG_CONST_SCALAR:
+		err = process_const_arg(env, reg, argno, meta);
+		if (err < 0) {
+			if (err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+						      "the function requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+						      reg_arg_name(env, argno));
+			return err;
+		}
+		break;
+	case ARG_SCALAR:
+		break;
 	case ARG_CONST_MAP_PTR:
 		/* bpf_map_xxx(map_ptr) call: remember that map_ptr */
 		err = process_map_ptr_arg(env, reg, argno, meta);
@@ -8915,7 +8990,7 @@ 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, meta,
 					      NULL);
 		if (err)
 			return err;
@@ -8947,7 +9022,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		 * Disable raw mode for bpf_map_peek_elem() on a bloom filter. The helper reads
 		 * the value buffer as an input rather than filling it.
 		 */
-		if (meta->func_id == BPF_FUNC_map_peek_elem &&
+		if (is_helper_call(meta, BPF_FUNC_map_peek_elem) &&
 		    meta->map.ptr->map_type == BPF_MAP_TYPE_BLOOM_FILTER)
 			meta->arg_raw_mem.regno = 0;
 
@@ -8955,9 +9030,79 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 					      arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
 					      false, meta, NULL);
 		break;
+	case ARG_PTR_TO_BTF_ID:
+	case ARG_PTR_TO_BTF_ID_SOCK_COMMON:
+	{
+		const u32 *arg_btf_id = fn->arg_btf_id[arg];
+		const struct btf *arg_btf = meta->btf ?: btf_vmlinux;
+
+		if (!meta->btf) {
+			const struct bpf_reg_types *compatible;
+
+			if (base_type(reg->type) != PTR_TO_BTF_ID)
+				break;
+
+			if (is_helper_call(meta, BPF_FUNC_kptr_xchg))
+				return map_kptr_match_type(env, meta->kptr_field, reg, regno) ?
+				       -EACCES : 0;
+
+			if (!arg_btf_id) {
+				compatible = compatible_reg_types[base_type(arg_type)];
+				if (!compatible->btf_id) {
+					verifier_bug(env, "missing arg compatible BTF ID");
+					return -EFAULT;
+				}
+				arg_btf_id = compatible->btf_id;
+			}
+			if (arg_btf_id == BPF_PTR_POISON) {
+				verbose(env, "verifier internal error:");
+				verbose(env, "%s has non-overwritten BPF_PTR_POISON type\n",
+					reg_arg_name(env, argno));
+				return -EACCES;
+			}
+		}
+
+		if (meta->btf && (!is_trusted_reg(env, reg) ||
+				  bpf_type_has_unsafe_modifiers(reg->type))) {
+			if (!(arg_type & MEM_RCU)) {
+				const char *actual_type, *arg_name, *expected_type;
+
+				expected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);
+				verbose(env, "%s must be referenced or trusted\n",
+					reg_arg_name(env, argno));
+				arg_name = reg_arg_name(env, argno);
+				actual_type = bpf_diag_reg_type_plain(env, reg->type);
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a pointer acquired from a verifier-tracked source, or call this function only inside the required protection if it accepts RCU pointers.",
+						      "the function requires a trusted or resource-owning pointer to %s, but %s is %s",
+						      expected_type, arg_name, actual_type);
+				return -EINVAL;
+			}
+			if (!is_rcu_reg(reg)) {
+				const char *actual_type, *arg_name, *expected_type;
+
+				expected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);
+				verbose(env, "%s must be a rcu pointer\n",
+					reg_arg_name(env, argno));
+				arg_name = reg_arg_name(env, argno);
+				actual_type = bpf_diag_reg_type_plain(env, reg->type);
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Use this function with a pointer that is valid in an RCU read lock region.",
+						      "the function requires an RCU-protected pointer to %s, but %s is %s",
+						      expected_type, arg_name, actual_type);
+				return -EINVAL;
+			}
+		}
+
+		err = process_arg_ptr_to_btf_id(env, reg, argno, arg_type, arg_btf,
+						*arg_btf_id, meta, insn_idx);
+		if (err < 0)
+			return err;
+		break;
+	}
 	case ARG_PTR_TO_PERCPU_BTF_ID:
 		if (!reg->btf_id) {
-			verbose(env, "Helper has invalid btf_id in R%d\n", regno);
+			verbose(env, "Helper has invalid btf_id in %s\n", reg_arg_name(env, argno));
 			return -EACCES;
 		}
 		meta->ret_btf = reg->btf;
@@ -8968,11 +9113,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			verbose(env, "can't spin_{lock,unlock} in rbtree cb\n");
 			return -EACCES;
 		}
-		if (meta->func_id == BPF_FUNC_spin_lock) {
+		if (is_helper_call(meta, BPF_FUNC_spin_lock)) {
 			err = process_spin_lock(env, reg, argno, PROCESS_SPIN_LOCK);
 			if (err)
 				return err;
-		} else if (meta->func_id == BPF_FUNC_spin_unlock) {
+		} else if (is_helper_call(meta, BPF_FUNC_spin_unlock)) {
 			err = process_spin_lock(env, reg, argno, 0);
 			if (err)
 				return err;
@@ -8986,45 +9131,274 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		if (err)
 			return err;
 		break;
+	case ARG_PTR_TO_CTX:
+		if (is_bpf_cast_to_kern_ctx_kfunc(meta)) {
+			err = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog));
+			if (err < 0)
+				return -EINVAL;
+			meta->ret_btf_id = err;
+		}
+		break;
+	case ARG_PTR_TO_ARENA:
+		break;
+	case ARG_PTR_TO_ALLOC_BTF_ID:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
+			if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
+				verbose(env, "%s expected for bpf_obj_drop()\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+		} else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {
+			if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) {
+				verbose(env, "%s expected for bpf_percpu_obj_drop()\n",
+					reg_arg_name(env, argno));
+				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, meta->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) {
+			meta->arg_btf = reg->btf;
+			meta->arg_btf_id = reg->btf_id;
+		}
+		break;
 	case ARG_PTR_TO_FUNC:
 		meta->subprogno = reg->subprogno;
 		break;
 	case ARG_PTR_TO_MEM:
+	{
+		enum bpf_access_type access_type;
+		bool known_memory;
+
 		/* The access to this pointer is only checked when we hit the
 		 * next is_mem_size argument below.
 		 */
-		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, NULL);
-			if (err)
-				return err;
-			if (arg_type & MEM_ALIGNED)
-				err = check_ptr_alignment(env, reg, 0, fn->arg_size[arg], true);
+		if (!(arg_type & MEM_FIXED_SIZE))
+			break;
+
+		access_type = arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ;
+		if (meta->btf)
+			access_type = BPF_READ | BPF_WRITE;
+
+		err = check_mem_reg(env, reg, argno, arg_size, access_type, meta, &known_memory);
+		if (err < 0) {
+			if (known_memory)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+					"Pass memory with at least the required number of accessible bytes and suitable read or write access.",
+					"the function expects %u bytes of memory, but the verifier cannot prove that %s provides a range of that size with the required read or write access",
+					arg_size,
+					bpf_diag_reg_type_plain(env, reg->type));
+			else
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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 function expects %u bytes of memory, but it is %s and not verifier-known memory",
+					arg_size,
+					bpf_diag_reg_type_plain(env, reg->type));
+			return err;
 		}
+		if (arg_type & MEM_ALIGNED)
+			err = check_ptr_alignment(env, reg, 0, arg_size, true);
 		break;
+	}
+	case ARG_CONST_MEM_SIZE:
+		err = process_const_arg(env, reg, argno, meta);
+		if (err < 0) {
+			if (err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+						      "the function requires this memory size to be a verifier-known constant, but %s is variable on this path",
+						      reg_arg_name(env, argno));
+			return err;
+		}
+		fallthrough;
 	case ARG_MEM_SIZE:
-		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, 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, NULL);
-		break;
-	case ARG_PTR_TO_DYNPTR:
-		err = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta->func_id),
-					  arg_type, &meta->ref_obj, &meta->dynptr);
+	{
+		struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, arg - 1);
+		argno_t buff_argno = argno_from_arg(arg);
+		enum bpf_mem_size_failure failure;
+		const char *buff_arg, *size_arg;
+		bool zero_size_allowed;
+		u32 access_type;
+
+		if (meta->btf && bpf_register_is_null(buff_reg))
+			break;
+
+		access_type = fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ;
+		if (meta->btf)
+			access_type = BPF_READ | BPF_WRITE;
+
+		zero_size_allowed = meta->btf || base_type(arg_type) == ARG_MEM_SIZE_OR_ZERO;
+
+		err = check_mem_size_reg(env, buff_reg, reg, buff_argno, argno,
+					 access_type, zero_size_allowed, meta, &failure);
+		if (!err)
+			break;
+
+		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, meta->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 provide a verifier-accessible range of the requested length",
+					      size_arg, buff_arg);
+		} else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {
+			if (reg_smin(reg) < 0)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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(reg));
+			else if (!zero_size_allowed && reg_umin(reg) == 0)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+					"Ensure the memory size is non-zero before this call.",
+					"the memory size in %s may be zero, but the function requires a non-zero size",
+					size_arg);
+			else
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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(reg), BPF_MAX_VAR_SIZ);
+		}
+		break;
+	}
+	case ARG_PTR_TO_DYNPTR: {
+		if (is_bpf_dynptr_clone_kfunc(meta) &&
+		    (arg_type & MEM_UNINIT)) {
+			enum bpf_dynptr_type parent_type = meta->dynptr.type;
+
+			if (parent_type == BPF_DYNPTR_TYPE_INVALID) {
+				verifier_bug(env, "no dynptr type for parent of clone");
+				return -EFAULT;
+			}
+
+			arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
+		}
+
+		err = process_dynptr_func(env, reg, argno, insn_idx, arg_type, meta);
 		if (err)
 			return err;
 		break;
+	}
+	case ARG_PTR_TO_ITER:
+		if (is_bpf_iter_css_task_new_kfunc(meta) &&
+		    !check_css_task_iter_allowlist(env)) {
+			verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n");
+			return -EINVAL;
+		}
+		err = process_iter_arg(env, reg, argno, insn_idx, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_LIST_HEAD:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
+		    !reg_is_referenced(env, reg)) {
+			verbose(env, "allocated object must be referenced\n");
+			return -EINVAL;
+		}
+		err = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RB_ROOT:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
+		    !reg_is_referenced(env, reg)) {
+			verbose(env, "allocated object must be referenced\n");
+			return -EINVAL;
+		}
+		err = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_LIST_NODE:
+		if (!(is_kfunc_arg_nonown_allowed(meta->btf, btf_arg) &&
+		      type_is_non_owning_ref(reg->type) && !reg_is_referenced(env, reg))) {
+			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
+				verbose(env, "%s expected pointer to allocated object\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+			if (!reg_is_referenced(env, reg)) {
+				verbose(env, "allocated object must be referenced\n");
+				return -EINVAL;
+			}
+		}
+		err = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RB_NODE:
+		if (is_bpf_rbtree_add_kfunc(meta->func_id)) {
+			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
+				verbose(env, "%s expected pointer to allocated object\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+			if (!reg_is_referenced(env, reg)) {
+				verbose(env, "allocated object must be referenced\n");
+				return -EINVAL;
+			}
+		} else {
+			if (!type_is_non_owning_ref(reg->type) &&
+			    !reg_is_referenced(env, reg)) {
+				verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n",
+					meta->func_name);
+				return -EINVAL;
+			}
+			if (in_rbtree_lock_required_cb(env)) {
+				verbose(env, "%s not allowed in rbtree cb\n", meta->func_name);
+				return -EINVAL;
+			}
+		}
+		err = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
 	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		if (meta->btf && is_kfunc_arg_scalar_with_name(meta->btf, btf_arg,
+							       "rdonly_buf_size"))
+			meta->r0_rdonly = true;
 		err = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
-		if (err)
+		if (err < 0) {
+			if (meta->btf && err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a verifier-known constant size for this function's buffer argument.",
+						      "the function uses this argument as a return-buffer size, but %s is invalid or variable on this path",
+						      reg_arg_name(env, argno));
 			return err;
+		}
 		break;
+	case ARG_PTR_TO_REFCOUNTED_KPTR:
+	{
+		struct btf_record *rec;
+
+		if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
+			meta->arg_owning_ref = true;
+
+		rec = reg_btf_record(reg);
+		if (!rec) {
+			verifier_bug(env, "Couldn't find btf_record");
+			return -EFAULT;
+		}
+
+		if (rec->refcount_off < 0) {
+			verbose(env, "%s doesn't point to a type with bpf_refcount field\n",
+				reg_arg_name(env, argno));
+			return -EINVAL;
+		}
+
+		meta->arg_btf = reg->btf;
+		meta->arg_btf_id = reg->btf_id;
+		break;
+	}
 	case ARG_PTR_TO_CONST_STR:
 	{
 		err = check_arg_const_str(env, reg, argno);
@@ -9032,6 +9406,38 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			return err;
 		break;
 	}
+	case ARG_PTR_TO_WORKQUEUE:
+		err = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, &meta->map);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_TASK_WORK:
+		err = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, &meta->map);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_IRQ_FLAG:
+		err = process_irq_flag(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RES_SPIN_LOCK:
+	{
+		int flags;
+
+		if (in_rbtree_lock_required_cb(env)) {
+			verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n");
+			return -EACCES;
+		}
+
+		flags = get_bpf_res_spin_lock_kfunc_flags(meta);
+		if (!flags)
+			return -EFAULT;
+		err = process_spin_lock(env, reg, argno, flags);
+		if (err < 0)
+			return err;
+		break;
+	}
 	case ARG_KPTR_XCHG_DEST:
 		err = process_kptr_func(env, regno, meta);
 		if (err)
@@ -9042,6 +9448,37 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	return err;
 }
 
+static int check_func_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+			   int insn_idx)
+{
+	struct bpf_func_state *caller = cur_func(env);
+	const struct btf_param *args = NULL;
+	u32 arg, nargs = MAX_BPF_FUNC_REG_ARGS;
+	int err;
+
+	if (meta->btf) {
+		args = btf_params(meta->func_proto);
+		nargs = btf_type_vlen(meta->func_proto);
+	}
+
+	if (nargs > MAX_BPF_FUNC_REG_ARGS) {
+		err = check_outgoing_stack_args(env, caller, nargs, meta->func_name,
+						meta->btf, args);
+		if (err)
+			return err;
+	}
+
+	for (arg = 0; arg < nargs; arg++) {
+		if (meta->fn->arg_type[arg] == ARG_UNUSED)
+			break;
+		err = check_func_arg(env, arg, meta, insn_idx);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
 static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id)
 {
 	enum bpf_attach_type eatype = env->prog->expected_attach_type;
@@ -9339,7 +9776,7 @@ static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_a
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
-		if (fn->arg_type[i] == ARG_DONTCARE)
+		if (fn->arg_type[i] == ARG_UNUSED)
 			break;
 		if (!arg_type_is_raw_mem(fn->arg_type[i]))
 			continue;
@@ -9389,7 +9826,7 @@ static bool check_btf_id_ok(const struct bpf_func_proto *fn)
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
-		if (fn->arg_type[i] == ARG_DONTCARE)
+		if (fn->arg_type[i] == ARG_UNUSED)
 			break;
 		if (base_type(fn->arg_type[i]) == ARG_PTR_TO_BTF_ID)
 			return !!fn->arg_btf_id[i];
@@ -9412,7 +9849,7 @@ static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
 		enum bpf_arg_type arg_type = fn->arg_type[i];
 
-		if (arg_type == ARG_DONTCARE)
+		if (arg_type == ARG_UNUSED)
 			break;
 		if (base_type(arg_type) != ARG_PTR_TO_MEM)
 			continue;
@@ -9430,7 +9867,7 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
 		enum bpf_arg_type arg_type = fn->arg_type[i];
 
-		if (arg_type == ARG_DONTCARE)
+		if (arg_type == ARG_UNUSED)
 			break;
 		if (arg_type_is_release(arg_type)) {
 			if (meta->release_regno)
@@ -9442,9 +9879,42 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
 	return true;
 }
 
-static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
+static bool check_arg_prog_aux(struct bpf_verifier_env *env,
+			       const struct bpf_func_proto *proto)
 {
-	return check_raw_mode_ok(fn, meta) &&
+	bool seen = false;
+	argno_t argno;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(proto->arg_type); i++) {
+		if (proto->arg_type[i] == ARG_UNUSED)
+			break;
+		if (proto->arg_type[i] != ARG_PTR_TO_PROG_AUX)
+			continue;
+
+		if (seen) {
+			verifier_bug(env, "Only 1 prog->aux argument supported");
+			return false;
+		}
+
+		argno = argno_from_arg(i + 1);
+		if (reg_from_argno(argno) < 0) {
+			verbose(env, "%s prog->aux cannot be a stack argument\n",
+				reg_arg_name(env, argno));
+			return false;
+		}
+
+		seen = true;
+	}
+
+	return true;
+}
+
+static int check_func_proto(struct bpf_verifier_env *env, const struct bpf_func_proto *fn,
+			    struct bpf_call_arg_meta *meta)
+{
+	return check_arg_prog_aux(env, fn) &&
+	       check_raw_mode_ok(fn, meta) &&
 	       check_arg_pair_ok(fn) &&
 	       check_mem_arg_rw_flag_ok(fn) &&
 	       check_proto_release_reg(fn, meta) &&
@@ -9664,7 +10134,8 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 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;
+			if (!type_may_be_null(reg->type))
+				reg->id = 0;
 			reg->type &= ~MEM_ALLOC;
 			reg->type |= MEM_RCU;
 			bpf_diag_mod_end(env);
@@ -9763,12 +10234,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 	struct bpf_subprog_info *sub = subprog_info(env, 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;
+	struct bpf_call_arg_meta meta;
 	u32 i;
 	int ret, err;
 
+	/* Leave btf and func_id zero: this is neither a helper nor a kfunc. */
+	memset(&meta, 0, sizeof(meta));
+	meta.func_name = bpf_subprog_name(env, subprog);
+
 	ret = btf_prepare_func_args(env, subprog);
 	if (ret) {
 		if (bpf_in_stack_arg_cnt(sub) > 0) {
@@ -9797,7 +10272,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 		struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
 		struct bpf_subprog_arg_info *arg = &sub->args[i];
 
-		if (arg->arg_type == ARG_ANYTHING) {
+		if (arg->arg_type == ARG_SCALAR) {
 			if (reg->type != SCALAR_VALUE) {
 				bpf_log(log, "%s is not a scalar\n", reg_arg_name(env, argno));
 				return -EINVAL;
@@ -9821,7 +10296,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 				return -EINVAL;
 			}
 		} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
-			ret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);
+			ret = check_func_arg_reg_off(env, reg, argno, ARG_PTR_TO_MEM);
 			if (ret < 0)
 				return ret;
 			if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL,
@@ -9852,12 +10327,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 				return ret;
 
 			ret = process_dynptr_func(env, reg, argno, env->insn_idx,
-						  bpf_subprog_name(env, subprog), arg->arg_type,
-						  &ref_obj, NULL);
+						  arg->arg_type, &meta);
 			if (ret)
 				return ret;
 		} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
-			struct bpf_call_arg_meta meta;
 			int err;
 
 			if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) {
@@ -9867,10 +10340,12 @@ 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,
-					     bpf_subprog_name(env, subprog));
+			err = check_reg_type(env, reg, argno, arg->arg_type, &meta);
 			err = err ?: check_func_arg_reg_off(env, reg, argno, arg->arg_type);
+			if (!err && base_type(reg->type) == PTR_TO_BTF_ID)
+				err = process_arg_ptr_to_btf_id(env, reg, argno, arg->arg_type,
+								btf_vmlinux, arg->btf_id,
+								&meta, env->insn_idx);
 			if (err)
 				return err;
 		} else {
@@ -10987,7 +11462,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 
 	memset(&meta, 0, sizeof(meta));
 
-	err = check_func_proto(fn, &meta);
+	err = check_func_proto(env, fn, &meta);
 	if (err) {
 		verifier_bug(env, "incorrect func proto %s#%d", func_id_name(func_id), func_id);
 		return err;
@@ -11008,13 +11483,11 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 		env->insn_aux_data[insn_idx].non_sleepable = true;
 
 	meta.func_id = func_id;
+	meta.func_name = func_id_name(func_id);
 	meta.fn = fn;
-	/* check args */
-	for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
-		err = check_func_arg(env, i, &meta, insn_idx);
-		if (err)
-			return err;
-	}
+	err = check_func_args(env, &meta, insn_idx);
+	if (err)
+		return err;
 
 	err = record_func_map(env, &meta, func_id, insn_idx);
 	if (err)
@@ -11853,6 +12326,50 @@ static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,
 	return btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR);
 }
 
+static int resolve_func_arg_type(struct bpf_verifier_env *env,
+				 struct bpf_reg_state *reg, u32 arg,
+				 struct bpf_call_arg_meta *meta,
+				 enum bpf_arg_type *arg_type, u32 *arg_size)
+{
+	argno_t argno = argno_from_arg(arg + 1);
+	const struct btf_param *args;
+	const struct btf_type *ref_t, *resolve_ret;
+	const struct btf *btf;
+	const char *ref_tname;
+	u32 ref_id;
+
+	if (base_type(*arg_type) == ARG_PTR_TO_MAP_VALUE)
+		return resolve_map_arg_type(env, meta, arg_type);
+
+	if (base_type(*arg_type) != ARG_PTR_TO_BTF_ID)
+		return 0;
+
+	if (!meta->btf || arg_type_is_release(*arg_type) ||
+	    base_type(reg->type) == PTR_TO_BTF_ID ||
+	    reg2btf_ids[base_type(reg->type)])
+		return 0;
+
+	args = btf_params(meta->func_proto);
+	ref_id = *meta->fn->arg_btf_id[arg];
+	btf = is_kfunc_arg_map(meta->btf, &args[arg]) ? btf_vmlinux : meta->btf;
+	ref_t = btf_type_skip_modifiers(btf, ref_id, &ref_id);
+	ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+
+	if (!btf_type_is_scalar_struct(env, btf, ref_t))
+		return 0;
+
+	resolve_ret = btf_resolve_size(btf, ref_t, arg_size);
+	if (IS_ERR(resolve_ret)) {
+		verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
+			reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname,
+			PTR_ERR(resolve_ret));
+		return -EINVAL;
+	}
+	*arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | (*arg_type & PTR_MAYBE_NULL);
+
+	return 0;
+}
+
 static void btf_member_path_str(const struct btf *btf, const struct btf_member_path *path,
 				char *buf, size_t buf_sz)
 {
@@ -11872,34 +12389,6 @@ static void btf_member_path_str(const struct btf *btf, const struct btf_member_p
 	}
 }
 
-enum kfunc_ptr_arg_type {
-	KF_ARG_CONST_MEM_SIZE,
-	KF_ARG_MEM_SIZE,
-	KF_ARG_CONST,
-	KF_ARG_CONST_ALLOC_SIZE_OR_ZERO,
-	KF_ARG_ANYTHING,
-	KF_ARG_PTR_TO_CTX,
-	KF_ARG_PTR_TO_ALLOC_BTF_ID,    /* Allocated object */
-	KF_ARG_PTR_TO_REFCOUNTED_KPTR, /* Refcounted local kptr */
-	KF_ARG_PTR_TO_DYNPTR,
-	KF_ARG_PTR_TO_ITER,
-	KF_ARG_PTR_TO_LIST_HEAD,
-	KF_ARG_PTR_TO_LIST_NODE,
-	KF_ARG_PTR_TO_BTF_ID,	       /* Also covers reg2btf_ids conversions */
-	KF_ARG_PTR_TO_MEM,
-	KF_ARG_PTR_TO_CALLBACK,
-	KF_ARG_PTR_TO_RB_ROOT,
-	KF_ARG_PTR_TO_RB_NODE,
-	KF_ARG_PTR_TO_CONST_STR,
-	KF_ARG_CONST_MAP_PTR,
-	KF_ARG_PTR_TO_TIMER,
-	KF_ARG_PTR_TO_WORKQUEUE,
-	KF_ARG_PTR_TO_IRQ_FLAG,
-	KF_ARG_PTR_TO_RES_SPIN_LOCK,
-	KF_ARG_PTR_TO_TASK_WORK,
-	KF_ARG_PTR_TO_ARENA,
-};
-
 enum special_kfunc_type {
 	KF_bpf_obj_new_impl,
 	KF_bpf_obj_new,
@@ -11967,7 +12456,10 @@ enum special_kfunc_type {
 	KF_bpf_task_work_schedule_resume,
 	KF_bpf_arena_alloc_pages,
 	KF_bpf_arena_free_pages,
+	KF_bpf_arena_reserve_pages,
 	KF_bpf_session_is_return,
+	KF_bpf_stream_vprintk,
+	KF_bpf_stream_print_stack,
 };
 
 BTF_ID_LIST(special_kfunc_list)
@@ -12057,11 +12549,29 @@ BTF_ID(func, bpf_task_work_schedule_signal)
 BTF_ID(func, bpf_task_work_schedule_resume)
 BTF_ID(func, bpf_arena_alloc_pages)
 BTF_ID(func, bpf_arena_free_pages)
+BTF_ID(func, bpf_arena_reserve_pages)
 #ifdef CONFIG_BPF_EVENTS
 BTF_ID(func, bpf_session_is_return)
 #else
 BTF_ID_UNUSED
 #endif
+BTF_ID(func, bpf_stream_vprintk)
+BTF_ID(func, bpf_stream_print_stack)
+
+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]);
+}
+
+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_clone]);
+}
+
+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_iter_css_task_new]);
+}
 
 static bool is_bpf_obj_new_kfunc(u32 func_id)
 {
@@ -12124,52 +12634,63 @@ static bool is_kfunc_ret_null(struct bpf_call_arg_meta *meta)
 
 static bool is_kfunc_bpf_rcu_read_lock(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_lock];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_lock]);
 }
 
 static bool is_kfunc_bpf_rcu_read_unlock(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_unlock];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_unlock]);
 }
 
 static bool is_kfunc_bpf_preempt_disable(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_preempt_disable];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_disable]);
 }
 
 static bool is_kfunc_bpf_preempt_enable(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_enable]);
 }
 
 bool bpf_is_kfunc_pkt_changing(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_xdp_pull_data]);
 }
 
 static int
 get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
-		   const struct btf_param *args, int arg, int nargs)
+		   const struct btf_param *args, int arg, int nargs,
+		   struct bpf_func_proto *proto)
 {
-	const struct btf_type *t, *ref_t = NULL;
+	const struct btf_type *t, *ref_t = NULL, *resolve_ret;
+	const u32 *ref_id_ptr = NULL;
 	argno_t argno = argno_from_arg(arg + 1);
 	const char *ref_tname = NULL;
+	u32 ref_id, type_size;
 	int arg_type;
 
+	proto->arg_btf_id[arg] = NULL;
+
+	if (is_kfunc_arg_prog_aux(meta->btf, &args[arg]))
+		return ARG_PTR_TO_PROG_AUX;
+
+	if (is_kfunc_arg_ignore(meta->btf, &args[arg]) || is_kfunc_arg_implicit(meta, arg))
+		return ARG_IGNORE;
+
 	t = btf_type_skip_modifiers(meta->btf, args[arg].type, NULL);
 
 	/* Scalar arguments are classified from their BTF suffix/name alone. */
 	if (btf_type_is_scalar(t)) {
 		if (is_kfunc_arg_constant(meta->btf, &args[arg]))
-			return KF_ARG_CONST;
+			return ARG_CONST_SCALAR;
 		if (is_kfunc_arg_const_mem_size(meta->btf, &args[arg]))
-			return KF_ARG_CONST_MEM_SIZE;
+			return ARG_CONST_MEM_SIZE;
 		if (is_kfunc_arg_mem_size(meta->btf, &args[arg]))
-			return KF_ARG_MEM_SIZE;
+			return ARG_MEM_SIZE;
 		if (is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdonly_buf_size") ||
 		    is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdwr_buf_size"))
-			return KF_ARG_CONST_ALLOC_SIZE_OR_ZERO;
-		return KF_ARG_ANYTHING;
+			return ARG_CONST_ALLOC_SIZE_OR_ZERO;
+		return ARG_SCALAR;
 	}
 
 	if (!btf_type_is_ptr(t)) {
@@ -12177,54 +12698,69 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 			reg_arg_name(env, argno), btf_type_str(t));
 		return -EINVAL;
 	}
-	ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
+	/* Keep a pointer to the BTF field containing the resolved referent ID. */
+	ref_id_ptr = &t->type;
+	ref_t = btf_type_skip_modifiers(meta->btf, *ref_id_ptr, &ref_id);
+	while (*ref_id_ptr != ref_id)
+		ref_id_ptr = &btf_type_by_id(meta->btf, *ref_id_ptr)->type;
 	ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
 
 	/* In this function, we verify the kfunc's BTF as per the argument type,
 	 * leaving the rest of the verification with respect to the register
 	 * type to our caller. When a set of conditions hold in the BTF type of
-	 * arguments, we resolve it to a known kfunc_ptr_arg_type.
+	 * arguments, we resolve it to a known bpf_arg_type.
 	 */
-	if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_session_is_return] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_session_cookie])
-		arg_type = KF_ARG_PTR_TO_CTX;
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_is_return]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_cookie]))
+		arg_type = ARG_PTR_TO_CTX;
 	else if (btf_is_prog_ctx_type(&env->log, meta->btf, t, resolve_prog_type(env->prog), arg))
-		arg_type = KF_ARG_PTR_TO_CTX;
+		arg_type = ARG_PTR_TO_CTX;
 	else if (is_kfunc_arg_alloc_obj(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_ALLOC_BTF_ID;
+		arg_type = ARG_PTR_TO_ALLOC_BTF_ID;
 	else if (is_kfunc_arg_refcounted_kptr(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_REFCOUNTED_KPTR;
-	else if (is_kfunc_arg_dynptr(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_DYNPTR;
-	else if (is_kfunc_arg_iter(meta, arg, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_ITER;
+		arg_type = ARG_PTR_TO_REFCOUNTED_KPTR;
+	else if (is_kfunc_arg_dynptr(meta->btf, &args[arg])) {
+		arg_type = ARG_PTR_TO_DYNPTR;
+
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb]))
+			arg_type |= DYNPTR_TYPE_SKB;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_xdp]))
+			arg_type |= DYNPTR_TYPE_XDP;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb_meta]))
+			arg_type |= DYNPTR_TYPE_SKB_META;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_file]) ||
+			 is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_file_discard]))
+			/* OBJ_RELEASE for the latter comes from KF_RELEASE below */
+			arg_type |= DYNPTR_TYPE_FILE;
+	} else if (is_kfunc_arg_iter(meta, arg, &args[arg]))
+		arg_type = ARG_PTR_TO_ITER;
 	else if (is_kfunc_arg_list_head(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_LIST_HEAD;
+		arg_type = ARG_PTR_TO_LIST_HEAD;
 	else if (is_kfunc_arg_list_node(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_LIST_NODE;
+		arg_type = ARG_PTR_TO_LIST_NODE;
 	else if (is_kfunc_arg_rbtree_root(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RB_ROOT;
+		arg_type = ARG_PTR_TO_RB_ROOT;
 	else if (is_kfunc_arg_rbtree_node(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RB_NODE;
+		arg_type = ARG_PTR_TO_RB_NODE;
 	else if (is_kfunc_arg_const_str(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_CONST_STR;
+		arg_type = ARG_PTR_TO_CONST_STR;
 	else if (is_kfunc_arg_const_map(meta->btf, &args[arg]))
-		arg_type = KF_ARG_CONST_MAP_PTR;
+		arg_type = ARG_CONST_MAP_PTR;
 	else if (is_kfunc_arg_map(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_BTF_ID;
+		arg_type = ARG_PTR_TO_BTF_ID;
 	else if (is_kfunc_arg_wq(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_WORKQUEUE;
+		arg_type = ARG_PTR_TO_WORKQUEUE;
 	else if (is_kfunc_arg_timer(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_TIMER;
+		arg_type = ARG_PTR_TO_TIMER;
 	else if (is_kfunc_arg_task_work(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_TASK_WORK;
+		arg_type = ARG_PTR_TO_TASK_WORK;
 	else if (is_kfunc_arg_irq_flag(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_IRQ_FLAG;
+		arg_type = ARG_PTR_TO_IRQ_FLAG;
 	else if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;
+		arg_type = ARG_PTR_TO_RES_SPIN_LOCK;
 	else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_CALLBACK;
+		arg_type = ARG_PTR_TO_FUNC;
 	else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
 		if (!bpf_jit_supports_arena_args()) {
 			verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
@@ -12247,7 +12783,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 		 * whether the JIT rebases it to the arena base or preserves NULL.
 		 * The common nullable path below records that verifier property.
 		 */
-		arg_type = KF_ARG_PTR_TO_ARENA;
+		arg_type = ARG_PTR_TO_ARENA;
 	} else if (arg + 1 < nargs &&
 		 (is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
 		  is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
@@ -12257,10 +12793,10 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 				reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
 			return -EINVAL;
 		}
-		arg_type = KF_ARG_PTR_TO_MEM;
+		arg_type = ARG_PTR_TO_MEM;
 	} else if (btf_type_is_struct(ref_t))
-		/* A pointer to a struct without a size argument is classified as KF_ARG_PTR_TO_BTF_ID */
-		arg_type = KF_ARG_PTR_TO_BTF_ID;
+		/* A pointer to a struct without a size argument is classified as ARG_PTR_TO_BTF_ID */
+		arg_type = ARG_PTR_TO_BTF_ID;
 	else {
 		/*
 		 * Otherwise this is a fixed-size memory buffer supported by
@@ -12273,19 +12809,52 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 				reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
 			return -EINVAL;
 		}
-		arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
+		resolve_ret = btf_resolve_size(meta->btf, ref_t, &type_size);
+		if (IS_ERR(resolve_ret)) {
+			verbose(env,
+				"%s reference type('%s %s') size cannot be determined: %ld\n",
+				reg_arg_name(env, argno), btf_type_str(ref_t),
+				ref_tname, PTR_ERR(resolve_ret));
+			return -EINVAL;
+		}
+		proto->arg_size[arg] = type_size;
+		arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
 	}
 
+	if (is_kfunc_arg_uninit(meta->btf, &args[arg]))
+		arg_type |= MEM_UNINIT;
+
 	if (is_kfunc_arg_nullable(meta->btf, &args[arg]))
 		arg_type |= PTR_MAYBE_NULL;
 
+	/*
+	 * Only the first argument of a KF_RELEASE kfunc releases anything, and
+	 * bpf_fetch_kfunc_arg_meta() only ever records BPF_REG_1 for it.
+	 */
+	if (is_kfunc_release(meta) && arg == 0)
+		arg_type |= OBJ_RELEASE;
+
+	if (base_type(arg_type) == ARG_PTR_TO_BTF_ID) {
+		if (is_kfunc_arg_map(meta->btf, &args[arg]))
+			proto->arg_btf_id[arg] = reg2btf_ids[CONST_PTR_TO_MAP];
+		else
+			proto->arg_btf_id[arg] = ref_id_ptr;
+
+		/*
+		 * A KF_RCU kfunc accepts an RCU-protected pointer where it would
+		 * otherwise demand a referenced or trusted one. Other argument kinds
+		 * have their own provenance requirements and must not inherit MEM_RCU.
+		 */
+		if (is_kfunc_rcu(meta))
+			arg_type |= MEM_RCU;
+	}
+
 	return arg_type;
 }
 
 static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 			       struct bpf_func_proto *proto)
 {
-	const struct btf *btf = meta->btf;
 	const struct btf_param *args;
 	u32 i, nargs;
 	int arg_type;
@@ -12304,47 +12873,38 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
 	}
 
 	for (i = 0; i < nargs; i++) {
-		if (is_kfunc_arg_prog_aux(btf, &args[i]) ||
-		    is_kfunc_arg_ignore(btf, &args[i]) ||
-		    is_kfunc_arg_implicit(meta, i))
-			continue;
-
-		arg_type = get_kfunc_arg_type(env, meta, args, i, nargs);
+		arg_type = get_kfunc_arg_type(env, meta, args, i, nargs, proto);
 		if (arg_type < 0)
 			return arg_type;
 
 		proto->arg_type[i] = arg_type;
 	}
 
-	return 0;
+	return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
 }
 
-static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
-					struct bpf_reg_state *reg,
-					const struct btf_type *ref_t,
-					const char *ref_tname, u32 ref_id,
-					struct bpf_call_arg_meta *meta,
-					int arg, argno_t argno)
+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				     argno_t argno, enum bpf_arg_type arg_type,
+				     const struct btf *arg_btf, u32 arg_btf_id,
+				     struct bpf_call_arg_meta *meta, int insn_idx)
 {
-	const struct btf_type *reg_ref_t;
-	bool strict_type_match = false;
+	bool taking_projection, struct_same, strict_type_match = false;
+	const struct btf_type *arg_t, *reg_t;
+	const char *arg_tname, *reg_tname;
 	const struct btf *reg_btf;
-	const char *reg_ref_tname;
-	bool taking_projection;
-	bool struct_same;
-	u32 reg_ref_id;
+	u32 reg_btf_id;
 
 	if (base_type(reg->type) == PTR_TO_BTF_ID) {
 		reg_btf = reg->btf;
-		reg_ref_id = reg->btf_id;
+		reg_btf_id = reg->btf_id;
 	} else {
 		reg_btf = btf_vmlinux;
-		reg_ref_id = *reg2btf_ids[base_type(reg->type)];
+		reg_btf_id = *reg2btf_ids[base_type(reg->type)];
 	}
 
-	/* Enforce strict type matching for calls to kfuncs that are acquiring
-	 * or releasing a reference, or are no-cast aliases. We do _not_
-	 * enforce strict matching for kfuncs by default,
+	/*
+	 * Enforce strict type matching for arguments that release a reference,
+	 * or are no-cast aliases. We do _not_ enforce strict matching by default,
 	 * as we want to enable BPF programs to pass types that are bitwise
 	 * equivalent without forcing them to explicitly cast with something
 	 * like bpf_cast_to_kern_ctx().
@@ -12366,27 +12926,30 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
 	 * btf_struct_ids_match() to walk the struct at the 0th offset, and
 	 * resolve types.
 	 */
-	if ((is_kfunc_release(meta) && reg_is_referenced(env, reg)) ||
-	    btf_type_ids_nocast_alias(&env->log, reg_btf, reg_ref_id, meta->btf, ref_id))
+	if ((arg_type_is_release(arg_type) && !is_helper_call(meta, BPF_FUNC_sk_release)) ||
+	    (meta->btf && btf_type_ids_nocast_alias(&env->log, reg_btf, reg_btf_id,
+						    arg_btf, arg_btf_id)))
 		strict_type_match = true;
 
-	WARN_ON_ONCE(is_kfunc_release(meta) && !tnum_is_const(reg->var_off));
+	arg_t = btf_type_skip_modifiers(arg_btf, arg_btf_id, &arg_btf_id);
+	arg_tname = btf_name_by_offset(arg_btf, arg_t->name_off);
+	reg_t = btf_type_skip_modifiers(reg_btf, reg_btf_id, &reg_btf_id);
+	reg_tname = btf_name_by_offset(reg_btf, reg_t->name_off);
+
+	struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_btf_id,
+					  reg->var_off.value, arg_btf, arg_btf_id,
+					  strict_type_match, !type_is_alloc(reg->type));
 
-	reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, &reg_ref_id);
-	reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off);
-	struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_ref_id, reg->var_off.value,
-					   meta->btf, ref_id, strict_type_match,
-					   !type_is_alloc(reg->type));
 	/* If kfunc is accepting a projection type (ie. __sk_buff), it cannot
 	 * actually use it -- it must cast to the underlying type. So we allow
 	 * caller to pass in the underlying type.
 	 */
-	taking_projection = btf_is_projection_of(ref_tname, reg_ref_tname);
+	taking_projection = meta->btf && btf_is_projection_of(arg_tname, reg_tname);
 	if (!taking_projection && !struct_same) {
-		verbose(env, "kernel function %s %s expected pointer to %s %s but %s has a pointer to %s %s\n",
+		verbose(env, "%s %s expected pointer to %s %s but %s has a pointer to %s %s\n",
 			meta->func_name, reg_arg_name(env, argno),
-			btf_type_str(ref_t), ref_tname, reg_arg_name(env, argno),
-			btf_type_str(reg_ref_t), reg_ref_tname);
+			btf_type_str(arg_t), arg_tname,
+			reg_arg_name(env, argno), btf_type_str(reg_t), reg_tname);
 		return -EINVAL;
 	}
 	return 0;
@@ -12398,15 +12961,15 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
 	int err, spi, kfunc_class = IRQ_NATIVE_KFUNC;
 	bool irq_save;
 
-	if (meta->func_id == special_kfunc_list[KF_bpf_local_irq_save] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) {
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_save]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave])) {
 		irq_save = true;
-		if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
 			kfunc_class = IRQ_LOCK_KFUNC;
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_local_irq_restore] ||
-		   meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_restore]) ||
+		   is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])) {
 		irq_save = false;
-		if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
 			kfunc_class = IRQ_LOCK_KFUNC;
 	} else {
 		verifier_bug(env, "unknown irq flags kfunc");
@@ -12599,12 +13162,20 @@ static bool is_bpf_rbtree_api_kfunc(u32 btf_id)
 	       btf_id == special_kfunc_list[KF_bpf_rbtree_right];
 }
 
-static bool is_bpf_res_spin_lock_kfunc(u32 btf_id)
+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta)
 {
-	return btf_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore];
+	int flags = PROCESS_RES_LOCK;
+
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
+		flags |= PROCESS_SPIN_LOCK;
+	else if (!is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock]) &&
+		 !is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
+		return 0;
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
+		flags |= PROCESS_LOCK_IRQ;
+	return flags;
 }
 
 static bool kfunc_spin_allowed(struct bpf_verifier_env *env, s32 func_id, s16 offset)
@@ -12881,707 +13452,6 @@ static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env)
 	}
 }
 
-static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
-			    int insn_idx)
-{
-	const char *func_name = meta->func_name, *ref_tname;
-	struct bpf_func_state *caller = cur_func(env);
-	struct bpf_reg_state *regs = cur_regs(env);
-	const struct btf *btf = meta->btf;
-	const struct btf_param *args;
-	struct btf_record *rec;
-	u32 i, nargs;
-	int ret;
-
-	args = (const struct btf_param *)(meta->func_proto + 1);
-	nargs = btf_type_vlen(meta->func_proto);
-
-	ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
-	if (ret)
-		return ret;
-
-	/* Check that BTF function arguments match actual types that the
-	 * verifier sees.
-	 */
-	for (i = 0; i < nargs; i++) {
-		struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
-		const struct btf_type *t, *ref_t, *resolve_ret;
-		enum bpf_arg_type arg_type = ARG_DONTCARE;
-		argno_t argno = argno_from_arg(i + 1);
-		int regno = reg_from_argno(argno);
-		bool btf_id_fixed_off_ok = true;
-		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])) {
-			/* Reject repeated use bpf_prog_aux */
-			if (meta->arg_prog) {
-				verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc");
-				return -EFAULT;
-			}
-			if (regno < 0) {
-				verbose(env, "%s prog->aux cannot be a stack argument\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			meta->arg_prog = true;
-			cur_aux(env)->arg_prog = regno;
-			continue;
-		}
-
-		if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i))
-			continue;
-
-		t = btf_type_skip_modifiers(btf, args[i].type, NULL);
-
-		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, args[i].type);
-			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 value of type %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 (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) {
-			ret = mark_arg_precision(env, argno);
-			if (ret)
-				return ret;
-			continue;
-		}
-
-		if (is_kfunc_arg_map(btf, &args[i])) {
-			ref_id = *reg2btf_ids[CONST_PTR_TO_MAP];
-			ref_t = btf_type_by_id(btf_vmlinux, ref_id);
-			ref_tname = btf_name_by_offset(btf, ref_t->name_off);
-		}
-
-		switch (base_type(kf_arg_type)) {
-		case KF_ARG_CONST:
-		case KF_ARG_CONST_MEM_SIZE:
-		case KF_ARG_MEM_SIZE:
-		case KF_ARG_ANYTHING:
-		case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
-		case KF_ARG_PTR_TO_ALLOC_BTF_ID:
-		case KF_ARG_PTR_TO_BTF_ID:
-		case KF_ARG_CONST_MAP_PTR:
-		case KF_ARG_PTR_TO_ITER:
-		case KF_ARG_PTR_TO_LIST_HEAD:
-		case KF_ARG_PTR_TO_LIST_NODE:
-		case KF_ARG_PTR_TO_RB_ROOT:
-		case KF_ARG_PTR_TO_RB_NODE:
-		case KF_ARG_PTR_TO_MEM:
-		case KF_ARG_PTR_TO_CALLBACK:
-		case KF_ARG_PTR_TO_CONST_STR:
-		case KF_ARG_PTR_TO_WORKQUEUE:
-		case KF_ARG_PTR_TO_TIMER:
-		case KF_ARG_PTR_TO_TASK_WORK:
-		case KF_ARG_PTR_TO_IRQ_FLAG:
-		case KF_ARG_PTR_TO_RES_SPIN_LOCK:
-		case KF_ARG_PTR_TO_ARENA:
-			break;
-		case KF_ARG_PTR_TO_DYNPTR:
-			arg_type = ARG_PTR_TO_DYNPTR;
-			break;
-		case KF_ARG_PTR_TO_CTX:
-			arg_type = ARG_PTR_TO_CTX;
-			break;
-		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
-			arg_type = ARG_PTR_TO_BTF_ID;
-			btf_id_fixed_off_ok = false;
-			break;
-		default:
-			verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type);
-			return -EFAULT;
-		}
-
-		if (regno == meta->release_regno)
-			arg_type |= OBJ_RELEASE;
-		ret = __check_func_arg_reg_off(env, reg, argno, arg_type,
-					       btf_id_fixed_off_ok);
-		if (ret < 0)
-			return ret;
-
-		switch (base_type(kf_arg_type)) {
-		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 == -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 == -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;
-			}
-
-			if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {
-				ret = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog));
-				if (ret < 0)
-					return -EINVAL;
-				meta->ret_btf_id  = ret;
-			}
-			break;
-		case KF_ARG_PTR_TO_ARENA:
-			if (reg->type != PTR_TO_ARENA && reg->type != SCALAR_VALUE) {
-				verbose(env, "%s is not a pointer to arena or scalar\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			break;
-		case KF_ARG_PTR_TO_ALLOC_BTF_ID:
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
-					verbose(env, "%s expected for bpf_obj_drop()\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-			} else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {
-				if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) {
-					verbose(env, "%s expected for bpf_percpu_obj_drop()\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-			} 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) {
-				meta->arg_btf = reg->btf;
-				meta->arg_btf_id = reg->btf_id;
-			}
-			break;
-		case KF_ARG_PTR_TO_DYNPTR:
-		{
-			enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;
-
-			if (is_kfunc_arg_uninit(btf, &args[i]))
-				dynptr_arg_type |= MEM_UNINIT;
-
-			if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) {
-				dynptr_arg_type |= DYNPTR_TYPE_SKB;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) {
-				dynptr_arg_type |= DYNPTR_TYPE_XDP;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) {
-				dynptr_arg_type |= DYNPTR_TYPE_SKB_META;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {
-				dynptr_arg_type |= DYNPTR_TYPE_FILE;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {
-				dynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] &&
-				   (dynptr_arg_type & MEM_UNINIT)) {
-				enum bpf_dynptr_type parent_type = meta->dynptr.type;
-
-				if (parent_type == BPF_DYNPTR_TYPE_INVALID) {
-					verifier_bug(env, "no dynptr type for parent of clone");
-					return -EFAULT;
-				}
-
-				dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
-			}
-
-			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;
-		}
-		case KF_ARG_PTR_TO_ITER:
-			if (meta->func_id == special_kfunc_list[KF_bpf_iter_css_task_new]) {
-				if (!check_css_task_iter_allowlist(env)) {
-					verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n");
-					return -EINVAL;
-				}
-			}
-			ret = process_iter_arg(env, reg, argno, insn_idx, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_LIST_HEAD:
-			if (reg->type != PTR_TO_MAP_VALUE &&
-			    reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
-			    !reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-			ret = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RB_ROOT:
-			if (reg->type != PTR_TO_MAP_VALUE &&
-			    reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
-			    !reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-			ret = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_LIST_NODE:
-			if (is_kfunc_arg_nonown_allowed(btf, &args[i]) &&
-			    type_is_non_owning_ref(reg->type) && !reg_is_referenced(env, reg)) {
-				/* Allow bpf_list_front/back return value for
-				 * __nonown_allowed list-node arguments.
-				 */
-				goto check_ok;
-			}
-			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (!reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-check_ok:
-			ret = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RB_NODE:
-			if (is_bpf_rbtree_add_kfunc(meta->func_id)) {
-				if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-					verbose(env, "%s expected pointer to allocated object\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-				if (!reg_is_referenced(env, reg)) {
-					verbose(env, "allocated object must be referenced\n");
-					return -EINVAL;
-				}
-			} else {
-				if (!type_is_non_owning_ref(reg->type) &&
-				    !reg_is_referenced(env, reg)) {
-					verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n", func_name);
-					return -EINVAL;
-				}
-				if (in_rbtree_lock_required_cb(env)) {
-					verbose(env, "%s not allowed in rbtree cb\n", func_name);
-					return -EINVAL;
-				}
-			}
-
-			ret = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_CONST_MAP_PTR:
-			if (base_type(reg->type) != CONST_PTR_TO_MAP ||
-			    type_may_be_null(reg->type)) {
-				verbose(env, "pointer in %s isn't map pointer\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = process_map_ptr_arg(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_BTF_ID:
-			/* Only base_type is checked, further checks are done here */
-			if (base_type(reg->type) == PTR_TO_BTF_ID ||
-			    reg2btf_ids[base_type(reg->type)]) {
-				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;
-					}
-				}
-
-				ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);
-				if (ret < 0)
-					return ret;
-				break;
-			}
-
-			if (!btf_type_is_scalar_struct(env, meta->btf, ref_t)) {
-				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),
-					btf_type_str(ref_t), ref_tname);
-				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;
-			}
-
-			/*
-			 * If the register does not contain btf id but the argument type is a pointer to
-			 * scalar-only struct, allow verifying it as a fixed size memory.
-			 */
-			kf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
-			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",
-						reg_arg_name(env, argno), btf_type_str(ref_t),
-						ref_tname, PTR_ERR(resolve_ret));
-					return -EINVAL;
-				}
-				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:
-			ret = process_const_arg(env, reg, argno, meta);
-			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 memory size to be a verifier-known constant, but %s is variable on this path",
-							      reg_arg_name(env, argno));
-				return ret;
-			}
-			fallthrough;
-		case KF_ARG_MEM_SIZE:
-		{
-			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;
-			}
-
-			if (bpf_register_is_null(buff_reg))
-				break;
-
-			ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
-						 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;
-		}
-		case KF_ARG_PTR_TO_CALLBACK:
-			if (reg->type != PTR_TO_FUNC) {
-				verbose(env, "%s expected pointer to func\n", reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			meta->subprogno = reg->subprogno;
-			break;
-		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
-			if (!type_is_ptr_alloc_obj(reg->type)) {
-				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 an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field.",
-						      "the kfunc expects a pointer to a BPF-managed refcounted object, but %s is %s",
-						      reg_arg_name(env, argno),
-						      bpf_diag_reg_type_plain(env, reg->type));
-				return -EINVAL;
-			}
-			if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
-				meta->arg_owning_ref = true;
-
-			rec = reg_btf_record(reg);
-			if (!rec) {
-				verifier_bug(env, "Couldn't find btf_record");
-				return -EFAULT;
-			}
-
-			if (rec->refcount_off < 0) {
-				verbose(env, "%s doesn't point to a type with bpf_refcount field\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-
-			meta->arg_btf = reg->btf;
-			meta->arg_btf_id = reg->btf_id;
-			break;
-		case KF_ARG_PTR_TO_CONST_STR:
-			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);
-			if (ret)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_WORKQUEUE:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_TIMER:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = process_timer_func(env, reg, argno, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_TASK_WORK:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_IRQ_FLAG:
-			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);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RES_SPIN_LOCK:
-		{
-			int flags = PROCESS_RES_LOCK;
-
-			if (in_rbtree_lock_required_cb(env)) {
-				verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n");
-				return -EACCES;
-			}
-
-			if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s doesn't point to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-
-			if (!is_bpf_res_spin_lock_kfunc(meta->func_id))
-				return -EFAULT;
-			if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
-			    meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])
-				flags |= PROCESS_SPIN_LOCK;
-			if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||
-			    meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])
-				flags |= PROCESS_LOCK_IRQ;
-			ret = process_spin_lock(env, reg, argno, flags);
-			if (ret < 0)
-				return ret;
-			break;
-		}
-		}
-	}
-
-	return 0;
-}
-
 int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,
 			     s32 func_id,
 			     s16 offset,
@@ -13912,12 +13782,12 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 		struct btf_field *field = meta->arg_rbtree_root.field;
 
 		mark_reg_graph_node(regs, BPF_REG_0, &field->graph_root);
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx])) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_TRUSTED;
 		regs[BPF_REG_0].btf = desc_btf;
 		regs[BPF_REG_0].btf_id = meta->ret_btf_id;
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_rdonly_cast])) {
 		ret_t = btf_type_by_id(desc_btf, meta->arg_constant.value);
 		if (!ret_t) {
 			verbose(env, "Unknown type ID %lld passed to kfunc bpf_rdonly_cast\n",
@@ -13937,8 +13807,8 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 				"kfunc bpf_rdonly_cast type ID argument must be of a struct or void\n");
 			return -EINVAL;
 		}
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice] ||
-		   meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice]) ||
+		   is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice_rdwr])) {
 		enum bpf_type_flag type_flag = get_dynptr_type_flag(meta->dynptr.type);
 
 		mark_reg_known_zero(env, regs, BPF_REG_0);
@@ -13953,7 +13823,7 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 		/* PTR_MAYBE_NULL will be added when is_kfunc_ret_null is checked */
 		regs[BPF_REG_0].type = PTR_TO_MEM | type_flag;
 
-		if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice]) {
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice])) {
 			regs[BPF_REG_0].type |= MEM_RDONLY;
 		} else {
 			/* this will set env->seen_direct_write to true */
@@ -14073,7 +13943,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		insn_aux->non_sleepable = true;
 
 	/* Check the arguments */
-	err = check_kfunc_args(env, &meta, insn_idx);
+	err = check_func_args(env, &meta, insn_idx);
 	if (err < 0)
 		return err;
 
@@ -17972,7 +17842,7 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,
 		cs->is_void = fn->ret_type == RET_VOID;
 		cs->num_params = 0;
 		for (i = 0; i < ARRAY_SIZE(fn->arg_type); ++i) {
-			if (fn->arg_type[i] == ARG_DONTCARE)
+			if (fn->arg_type[i] == ARG_UNUSED)
 				break;
 			cs->num_params++;
 		}
@@ -19776,7 +19646,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 			}
 
 			/* Also ensure the callback only has a single scalar argument. */
-			if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_ANYTHING) {
+			if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_SCALAR) {
 				verbose(env, "exception cb only supports single integer argument\n");
 				ret = -EINVAL;
 				goto out;
@@ -19789,7 +19659,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 			if (arg->arg_type == ARG_PTR_TO_CTX) {
 				reg->type = PTR_TO_CTX;
 				mark_reg_known_zero(env, regs, i);
-			} else if (arg->arg_type == ARG_ANYTHING) {
+			} else if (arg->arg_type == ARG_SCALAR) {
 				reg->type = SCALAR_VALUE;
 				mark_reg_unknown(env, regs, i);
 			} else if (arg->arg_type == ARG_PTR_TO_DYNPTR) {
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
index 14d4c1793aed5..d74a9db54c9a3 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
@@ -13,13 +13,13 @@ struct {
 	const char *prog_name;
 	const char *err_msg;
 } test_bpf_nf_fail_tests[] = {
-	{ "alloc_release", "kernel function bpf_ct_release R1 expected pointer to STRUCT nf_conn but" },
-	{ "insert_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "lookup_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "set_timeout_after_insert", "kernel function bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "set_status_after_insert", "kernel function bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "change_timeout_after_alloc", "kernel function bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but" },
-	{ "change_status_after_alloc", "kernel function bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but" },
+	{ "alloc_release", "bpf_ct_release R1 expected pointer to STRUCT nf_conn but" },
+	{ "insert_insert", "bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "lookup_insert", "bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "set_timeout_after_insert", "bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "set_status_after_insert", "bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "change_timeout_after_alloc", "bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but" },
+	{ "change_status_after_alloc", "bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but" },
 	{ "write_not_allowlisted_field", "no write support to nf_conn at off" },
 	{ "lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted R2" },
 	{ "lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted R4" },
diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index 78566b817fd70..c32c6dab49bce 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -11,8 +11,8 @@ struct {
 	const char *prog_name;
 	const char *err_msg;
 } cb_refs_tests[] = {
-	{ "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
-	{ "leak_prog", "Possibly NULL pointer passed to helper R2" },
+	{ "underflow_prog", "R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_" },
+	{ "leak_prog", "Unreleased reference id=4 alloc_insn=3" }, /* alloc_insn=3{2,3} */
 	{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
 	{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
 };
diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
index 2b39cc1b09f9a..0063e60d6f2f5 100644
--- a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
@@ -70,7 +70,7 @@ static struct kfunc_test_params kfunc_tests[] = {
 	TC_FAIL(kfunc_call_test_get_mem_fail_oversized, 0, "allocation size exceeds u32 max"),
 	TC_FAIL(kfunc_call_test_get_mem_fail_not_const, 0, "is not a const"),
 	TC_FAIL(kfunc_call_test_mem_acquire_fail, 0, "acquire kernel function does not return PTR_TO_BTF_ID"),
-	TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "R1 expected pointer to ctx, but got scalar"),
+	TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "R1 type=scalar expected=ctx"),
 	TC_FAIL(kfunc_call_test_spin_lock_unsafe, 0, "function calls are not allowed while holding a lock"),
 
 	/* success cases */
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index f7f94ccebce27..b973814482481 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -54,6 +54,7 @@
 #include "verifier_iterating_callbacks.skel.h"
 #include "verifier_jeq_infer_not_null.skel.h"
 #include "verifier_jit_convergence.skel.h"
+#include "verifier_kfunc_packet_access.skel.h"
 #include "verifier_ld_ind.skel.h"
 #include "verifier_ldsx.skel.h"
 #include "verifier_leak_ptr.skel.h"
@@ -218,6 +219,7 @@ void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }
 void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }
 void test_verifier_jeq_infer_not_null(void)   { RUN(verifier_jeq_infer_not_null); }
 void test_verifier_jit_convergence(void)      { RUN(verifier_jit_convergence); }
+void test_verifier_kfunc_packet_access(void)  { RUN_TESTS(verifier_kfunc_packet_access); }
 void test_verifier_load_acquire(void)         { RUN(verifier_load_acquire); }
 void test_verifier_ld_ind(void)               { RUN(verifier_ld_ind); }
 void test_verifier_ldsx(void)                  { RUN(verifier_ldsx); }
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
index 50609f3b0564a..6578cf12fa27d 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -205,7 +205,7 @@ int arena_arg_no_arena(void *ctx)
 SEC("syscall")
 __arch_x86_64
 __arch_arm64
-__failure __msg("is not a pointer to arena or scalar")
+__failure __msg("R1 type=fp expected=arena, scalar")
 int arena_arg_bad_reg(void *ctx)
 {
 	u64 buf = 0;
diff --git a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
index efe7bcae70f85..ede6a17d7da30 100644
--- a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
@@ -64,7 +64,7 @@ int BPF_PROG(cgrp_kfunc_acquire_no_null_check, struct cgroup *cgrp, const char *
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("R1 is fp expected STRUCT cgroup")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(cgrp_kfunc_acquire_fp, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *acquired, *stack_cgrp = (struct cgroup *)&path;
@@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *kptr;
@@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *acquired = (struct cgroup *)&path;
@@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)
 {
 	/* Cannot release trusted cgroup pointer which was not acquired. */
diff --git a/tools/testing/selftests/bpf/progs/cpumask_failure.c b/tools/testing/selftests/bpf/progs/cpumask_failure.c
index 4628feb53d861..c89c88db39d14 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_failure.c
+++ b/tools/testing/selftests/bpf/progs/cpumask_failure.c
@@ -183,7 +183,7 @@ int BPF_PROG(test_global_mask_no_null_check, struct task_struct *task, u64 clone
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to helper R2")
+__failure __msg("release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
 int BPF_PROG(test_global_mask_rcu_no_null_check, struct task_struct *task, u64 clone_flags)
 {
 	struct bpf_cpumask *prev, *curr;
@@ -243,7 +243,7 @@ int BPF_PROG(test_populate_invalid_destination, struct task_struct *task, u64 cl
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("leads to invalid memory access")
+__failure __msg("R2 type=scalar expected=fp")
 int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_flags)
 {
 	void *garbage = (void *)0x123456;
diff --git a/tools/testing/selftests/bpf/progs/irq.c b/tools/testing/selftests/bpf/progs/irq.c
index a4a007866a332..53df6d248e267 100644
--- a/tools/testing/selftests/bpf/progs/irq.c
+++ b/tools/testing/selftests/bpf/progs/irq.c
@@ -15,7 +15,7 @@ struct bpf_res_spin_lock lockA __hidden SEC(".data.A");
 struct bpf_res_spin_lock lockB __hidden SEC(".data.B");
 
 SEC("?tc")
-__failure __msg("R1 doesn't point to an irq flag on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int irq_save_bad_arg(struct __sk_buff *ctx)
 {
 	bpf_local_irq_save(&global_flags);
@@ -23,7 +23,7 @@ int irq_save_bad_arg(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 doesn't point to an irq flag on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int irq_restore_bad_arg(struct __sk_buff *ctx)
 {
 	bpf_local_irq_restore(&global_flags);
diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c
index c6699159dacdd..65d4c6e01f932 100644
--- a/tools/testing/selftests/bpf/progs/iters.c
+++ b/tools/testing/selftests/bpf/progs/iters.c
@@ -1688,7 +1688,7 @@ int iter_subprog_check_stacksafe(const void *ctx)
 struct bpf_iter_num global_it;
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_new_bad_arg(const void *ctx)
 {
 	bpf_iter_num_new(&global_it, 0, 1);
@@ -1696,7 +1696,7 @@ int iter_new_bad_arg(const void *ctx)
 }
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_next_bad_arg(const void *ctx)
 {
 	bpf_iter_num_next(&global_it);
@@ -1704,7 +1704,7 @@ int iter_next_bad_arg(const void *ctx)
 }
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_destroy_bad_arg(const void *ctx)
 {
 	bpf_iter_num_destroy(&global_it);
diff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c
index 76012dbbdb413..f65cc9766633e 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c
@@ -105,8 +105,7 @@ int iter_next_rcu_not_trusted(const void *ctx)
 }
 
 SEC("raw_tp/sys_enter")
-__failure __msg("R1 cannot write into rdonly_mem")
-/* Message should not be 'R1 cannot write into rdonly_trusted_mem' */
+__failure __msg("R1 type=rdonly_mem expected=fp")
 int iter_next_ptr_mem_not_trusted(const void *ctx)
 {
 	struct bpf_iter_num num_it;
@@ -135,7 +134,7 @@ int iter_ret_rcu_test_protected(const void *ctx)
 }
 
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
-__failure __msg("R1 type=rcu_ptr_or_null_ expected=")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_ret_rcu_test_type(const void *ctx)
 {
 	struct task_struct *p;
@@ -158,7 +157,7 @@ int iter_ret_rcu_test_protected_nostruct(const void *ctx)
 }
 
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
-__failure __msg("R1 type=rdonly_rcu_mem_or_null expected=")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_ret_rcu_test_type_nostruct(const void *ctx)
 {
 	void *p;
diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
index eee35d203b66f..ac4003bfb8b09 100644
--- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
@@ -149,7 +149,7 @@ int reject_bad_type_match(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 type=untrusted_ptr_or_null_ expected=percpu_ptr_")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int marked_as_untrusted_or_null(struct __sk_buff *ctx)
 {
 	struct map_value *v;
@@ -217,7 +217,7 @@ int reject_kptr_xchg_on_unref(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 type=rcu_ptr_or_null_ expected=percpu_ptr_")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int mark_ref_as_untrusted_or_null(struct __sk_buff *ctx)
 {
 	struct map_value *v;
@@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
+__failure __msg("release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
 int reject_untrusted_xchg(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *p;
@@ -291,7 +291,7 @@ int reject_bad_type_xchg(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("invalid kptr access, R2 type=trusted_ptr_prog_test_ref_kfunc")
+__failure __msg("R2 must have zero offset when passed to release func")
 int reject_member_of_ref_xchg(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *ref_ptr;
@@ -364,7 +364,7 @@ int kptr_xchg_ref_state(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("Possibly NULL pointer passed to helper R2")
+__success
 int kptr_xchg_possibly_null(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *p;
diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index 3e0d4f687aaad..23019023511af 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -118,8 +118,7 @@ int atomic_rmw_not_ok(void *ctx)
 
 SEC("socket")
 __failure
-__msg("invalid access to memory, mem_size=0 off=0 size=4")
-__msg("R1 min value is outside of the allowed memory range")
+__msg("R1 type=rdonly_untrusted_mem expected=fp")
 int kfunc_param_not_ok(void *ctx)
 {
 	int *p;
diff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
index 3701f4ea58c75..57615b0f0c25d 100644
--- a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
+++ b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
@@ -24,6 +24,7 @@ struct val_600b_t {
 struct elem {
 	long sum;
 	struct val_t __percpu_kptr *pc;
+	struct val_t __percpu_kptr *pc2;
 };
 
 struct {
@@ -46,6 +47,8 @@ struct {
 
 struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
 void bpf_task_release(struct task_struct *p) __ksym;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
 
 long ret;
 
@@ -123,6 +126,38 @@ int BPF_PROG(test_array_map_3)
 	return 0;
 }
 
+SEC("?fentry.s/bpf_fentry_test1")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+int BPF_PROG(reject_nullable_percpu_xchg_alias)
+{
+	struct val_t __percpu_kptr *p1, *p2, *old;
+	struct val_t *v;
+	struct elem *e;
+	int index = 0;
+
+	e = bpf_map_lookup_elem(&array, &index);
+	if (!e)
+		return 0;
+
+	p1 = bpf_percpu_obj_new(struct val_t);
+	p2 = bpf_percpu_obj_new(struct val_t);
+
+	bpf_rcu_read_lock();
+	old = bpf_kptr_xchg(&e->pc, p1);
+	if (old)
+		bpf_percpu_obj_drop(old);
+	old = bpf_kptr_xchg(&e->pc2, p2);
+	if (old)
+		bpf_percpu_obj_drop(old);
+
+	if (p1) {
+		v = bpf_this_cpu_ptr(p2);
+		v->b = 1;
+	}
+	bpf_rcu_read_unlock();
+	return 0;
+}
+
 SEC("?fentry.s/bpf_fentry_test1")
 __failure __msg("R1 expected for bpf_percpu_obj_drop()")
 int BPF_PROG(test_array_map_4)
diff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c
index 4504608196abd..08709f23ec0f1 100644
--- a/tools/testing/selftests/bpf/progs/rbtree_fail.c
+++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c
@@ -180,7 +180,7 @@ long rbtree_api_use_unchecked_remove_retval(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
+__failure __msg("R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_api_add_release_unlock_escape(void *ctx)
 {
 	struct node_data *n;
@@ -204,7 +204,7 @@ long rbtree_api_add_release_unlock_escape(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
+__failure __msg("R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_api_first_release_unlock_escape(void *ctx)
 {
 	struct bpf_rb_node *res;
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 338e43822ffec..e80f78fae2276 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -118,8 +118,8 @@ long refcount_acquire_maybe_null(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 is neither owning or non-owning ref")
-__msg("expects a pointer to a BPF-managed refcounted object, but R1 is a context pointer")
+__failure __msg("R1 type=ctx expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
+__msg("type ctx, but this argument accepts ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long refcount_acquire_non_object(void *ctx)
 {
 	return bpf_refcount_acquire(ctx) != NULL;
@@ -159,8 +159,7 @@ long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx)
 
 SEC("?syscall")
 __failure
-__msg("bpf_rbtree_remove can only take non-owning or refcounted "
-      "bpf_rb_node pointer")
+__msg("R2 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_remove_after_rcu_unlock(void *ctx)
 {
 	struct map_value_rcu_graph *mapval;
@@ -190,7 +189,7 @@ long rbtree_remove_after_rcu_unlock(void *ctx)
 }
 
 SEC("?syscall")
-__failure __msg("R1 is neither owning or non-owning ref")
+__failure __msg("R1 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long refcount_acquire_after_rcu_unlock(void *ctx)
 {
 	struct map_value_refcount_only *mapval;
diff --git a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
index 330682a88c161..8fd591bd1f6cf 100644
--- a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
+++ b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
@@ -24,7 +24,7 @@ struct bpf_spin_lock lock __hidden SEC(".data.A");
 struct bpf_res_spin_lock res_lock __hidden SEC(".data.B");
 
 SEC("?tc")
-__failure __msg("point to map value or allocated object")
+__failure __msg("R1 type=untrusted_ptr_ expected=map_value, ptr_")
 int res_spin_lock_arg(struct __sk_buff *ctx)
 {
 	struct arr_elem *elem;
diff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c
index 21428bb1ee597..10ebb4a7f105a 100644
--- a/tools/testing/selftests/bpf/progs/stream_fail.c
+++ b/tools/testing/selftests/bpf/progs/stream_fail.c
@@ -23,7 +23,7 @@ int stream_vprintk_scalar_arg(void *ctx)
 }
 
 SEC("syscall")
-__failure __msg("R2 doesn't point to a const string")
+__failure __msg("R2 type=ctx expected=map_value")
 int stream_vprintk_string_arg(void *ctx)
 {
 	bpf_stream_vprintk(BPF_STDOUT, ctx, NULL, 0);
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
index f96b0c13ed1a5..12c8ac6099cae 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
@@ -50,7 +50,7 @@ int BPF_PROG(task_kfunc_acquire_untrusted, struct task_struct *task, u64 clone_f
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("R1 is fp expected STRUCT task_struct")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(task_kfunc_acquire_fp, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *acquired, *stack_task = (struct task_struct *)&clone_flags;
@@ -179,7 +179,7 @@ int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_f
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *acquired = (struct task_struct *)&clone_flags;
@@ -225,7 +225,7 @@ int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_flags)
 {
 	/* Cannot release trusted task pointer which was not acquired. */
@@ -333,7 +333,7 @@ int BPF_PROG(task_access_comm2, struct task_struct *task, u64 clone_flags)
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("write into memory")
+__failure __msg("only read is supported")
 int BPF_PROG(task_access_comm3, struct task_struct *task, u64 clone_flags)
 {
 	bpf_probe_read_kernel(task->comm, 16, task->comm);
@@ -353,7 +353,7 @@ int BPF_PROG(task_access_comm4, struct task_struct *task, const char *buf, bool
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *local;
diff --git a/tools/testing/selftests/bpf/progs/task_work_fail.c b/tools/testing/selftests/bpf/progs/task_work_fail.c
index 3186e7b4b24e0..bc56bdaca780b 100644
--- a/tools/testing/selftests/bpf/progs/task_work_fail.c
+++ b/tools/testing/selftests/bpf/progs/task_work_fail.c
@@ -58,7 +58,7 @@ int mismatch_map(struct pt_regs *args)
 }
 
 SEC("perf_event")
-__failure __msg("R2 doesn't point to a map value")
+__failure __msg("R2 type=fp expected=map_value")
 int no_map_task_work(struct pt_regs *args)
 {
 	struct task_struct *task;
diff --git a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
index bf48fc43c7ab6..f7a83e5024543 100644
--- a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
+++ b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
@@ -40,7 +40,7 @@ int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size,
 }
 
 SEC("?lsm.s/bpf")
-__failure __msg("R1 expected pointer to stack or const struct bpf_dynptr")
+__failure __msg("R1 type=map_value expected=fp, dynptr_ptr")
 int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
 {
 	static struct bpf_dynptr val;
diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c
index 7856dad3d1f38..9d42ba8244082 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ctx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c
@@ -208,7 +208,7 @@ __naked void null_check_7_ctx_bind(void)
 
 SEC("cgroup/post_bind4")
 __description("pass ctx or null check, 8: null (bind)")
-__failure __msg("R1 type=scalar expected=ctx")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void null_check_8_null_bind(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index a3d2af8dc8396..b277b1efb8c7b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -177,7 +177,7 @@ __weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
 
 SEC("?tp_btf/task_newtask")
 __failure __log_level(2)
-__msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
 {
 	return subprog_trusted_destroy(task);
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
index 343fc08d97479..d1452ef6f2f9a 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
@@ -621,7 +621,7 @@ l0_%=:	exit;						\
 
 SEC("tracepoint")
 __description("helper access to variable memory: size = 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)")
-__failure __msg("R1 type=scalar expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void ptr_to_mem_or_null_8(void)
 {
 	asm volatile ("					\
@@ -637,7 +637,7 @@ __naked void ptr_to_mem_or_null_8(void)
 
 SEC("tracepoint")
 __description("helper access to variable memory: size > 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)")
-__failure __msg("R1 type=scalar expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void ptr_to_mem_or_null_9(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
index 71cee3f583243..12786b72c6948 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
@@ -258,7 +258,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("helper access to packet: test11, cls unsuitable helper 1")
-__failure __msg("helper access to the packet")
+__failure __msg("function access to the packet")
 __naked void test11_cls_unsuitable_helper_1(void)
 {
 	asm volatile ("					\
@@ -283,7 +283,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("helper access to packet: test12, cls unsuitable helper 2")
-__failure __msg("helper access to the packet")
+__failure __msg("function access to the packet")
 __naked void test12_cls_unsuitable_helper_2(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c
new file mode 100644
index 0000000000000..88009566d92f9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct nf_conn *bpf_skb_ct_lookup(struct __sk_buff *skb_ctx,
+				  struct bpf_sock_tuple *bpf_tuple,
+				  u32 tuple__sz, struct bpf_ct_opts *opts,
+				  u32 opts__sz) __ksym;
+void bpf_ct_release(struct nf_conn *nfct) __ksym;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("tc")
+__description("kfunc packet write requests writable skb")
+__success
+/* bpf_unclone_prologue() */
+__xlated("r6 = *(u8 *)(r1 +{{[0-9]+}})")
+__xlated("...")
+__xlated("w6 &= {{(1|128)}}")
+__xlated("...")
+__xlated("if r6 == 0x0 goto")
+__xlated("r6 = r1")
+__xlated("r2 ^= r2")
+__xlated("call")
+__xlated("if r0 == 0x0 goto")
+__xlated("w0 = 2")
+__xlated("...")
+__xlated("exit")
+__xlated("r1 = r6")
+int kfunc_packet_write(struct __sk_buff *skb)
+{
+	void *data_end = (void *)(long)skb->data_end;
+	void *data = (void *)(long)skb->data;
+	struct bpf_sock_tuple tuple = {};
+	struct nf_conn *nfct;
+
+	if (data + sizeof(struct bpf_ct_opts) > data_end)
+		return 0;
+
+	/* An invalid tuple size makes bpf_skb_ct_lookup() write opts->error. */
+	nfct = bpf_skb_ct_lookup(skb, &tuple, 1, data, sizeof(struct bpf_ct_opts));
+	if (nfct)
+		bpf_ct_release(nfct);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
index 401152b2b64fc..bc3dfdc1a5363 100644
--- a/tools/testing/selftests/bpf/progs/verifier_live_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
@@ -246,7 +246,7 @@ static __used __naked void read_first_param2(void)
 SEC("socket")
 __flag(BPF_F_TEST_STATE_FREQ)
 __failure
-__msg("R1 type=scalar expected=map_ptr")
+__msg("Possibly NULL pointer passed to trusted R1")
 __naked void caller_stack_pruning_callback(void)
 {
 	asm volatile (
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 d3be69a9a7557..621248a02a1f9 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
@@ -154,8 +154,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 __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void on_the_inner_map_pointer(void)
 {
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
index c01abf54923d3..4b1eadddd89cd 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
@@ -58,7 +58,7 @@ int mapofmaps_value_as_helper_mem_buf(struct __sk_buff *skb)
 }
 
 SEC("?tc")
-__failure __msg("type=map_ptr_or_null expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int mapofmaps_value_as_helper_fixed_mem(struct __sk_buff *skb)
 {
 	char th[sizeof(struct tcphdr)] = {};
diff --git a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
index 199ad18f8eb58..799db6f5713b0 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
@@ -344,7 +344,7 @@ __naked void potential_reference_to_system_key(void)
 
 SEC("tc")
 __description("reference tracking: release reference without check")
-__failure __msg("type=sock_or_null expected=sock")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void tracking_release_reference_without_check(void)
 {
 	asm volatile (
@@ -363,7 +363,7 @@ __naked void tracking_release_reference_without_check(void)
 
 SEC("tc")
 __description("reference tracking: release reference to sock_common without check")
-__failure __msg("type=sock_common_or_null expected=sock")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void to_sock_common_without_check(void)
 {
 	asm volatile (
@@ -1288,7 +1288,7 @@ l1_%=:	r1 = r6;					\
 
 SEC("tc")
 __description("reference tracking: bpf_sk_release(listen_sk)")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_release_listen_sk(void)
 {
 	asm volatile (
diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c
index 4f2f3209eec81..2a136c917680f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sock.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sock.c
@@ -110,7 +110,7 @@ l0_%=:	r0 = *(u32*)(r1 + %[bpf_sock_type]);		\
 
 SEC("cgroup/skb")
 __description("bpf_sk_fullsock(skb->sk): no !skb->sk check")
-__failure __msg("type=sock_common_or_null expected=sock_common")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void sk_no_skb_sk_check_1(void)
 {
@@ -466,7 +466,7 @@ l1_%=:	r0 = *(u32*)(r0 + %[bpf_sock_rx_queue_mapping__end]);\
 
 SEC("cgroup/skb")
 __description("bpf_tcp_sock(skb->sk): no !skb->sk check")
-__failure __msg("type=sock_common_or_null expected=sock_common")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void sk_no_skb_sk_check_2(void)
 {
@@ -603,7 +603,7 @@ l2_%=:	r0 = *(u32*)(r0 + %[bpf_tcp_sock_snd_cwnd]);	\
 
 SEC("tc")
 __description("bpf_sk_release(skb->sk)")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_release_skb_sk(void)
 {
 	asm volatile ("					\
@@ -620,7 +620,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("bpf_sk_release(bpf_sk_fullsock(skb->sk))")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_fullsock_skb_sk(void)
 {
 	asm volatile ("					\
@@ -644,7 +644,7 @@ l1_%=:	r1 = r0;					\
 
 SEC("tc")
 __description("bpf_sk_release(bpf_tcp_sock(skb->sk))")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_tcp_sock_skb_sk(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index 8f0c45421f893..b5f456d57669e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -28,7 +28,7 @@ int BPF_PROG(get_task_exe_file_kfunc_null)
 }
 
 SEC("lsm.s/inode_getxattr")
-__failure __msg("R1 is fp expected STRUCT task_struct")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(get_task_exe_file_kfunc_fp)
 {
 	u64 x;
@@ -80,7 +80,7 @@ int BPF_PROG(get_task_exe_file_kfunc_unreleased)
 }
 
 SEC("lsm.s/file_open")
-__failure __msg("release kfunc bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(put_file_kfunc_unacquired, struct file *file)
 {
 	/* Can't release an unacquired pointer. */
@@ -128,7 +128,7 @@ int BPF_PROG(path_d_path_kfunc_untrusted_from_current)
 }
 
 SEC("lsm.s/file_open")
-__failure __msg("kernel function bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
+__failure __msg("bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
 int BPF_PROG(path_d_path_kfunc_type_mismatch, struct file *file)
 {
 	bpf_path_d_path((struct path *)&file->f_task_work, buf, sizeof(buf));
diff --git a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
index d4d0f1610853a..ec4e0f3ff7920 100644
--- a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
+++ b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
@@ -42,7 +42,7 @@ int wakeup_source_access_lock_fields(void *ctx)
 }
 
 SEC("syscall")
-__failure __msg("release kfunc bpf_wakeup_sources_read_unlock expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_")
 int wakeup_source_unlock_no_lock(void *ctx)
 {
 	struct bpf_ws_lock *lock = (void *)0x1;
diff --git a/tools/testing/selftests/bpf/progs/wq_failures.c b/tools/testing/selftests/bpf/progs/wq_failures.c
index 32dc8827e128b..bd30217579d4d 100644
--- a/tools/testing/selftests/bpf/progs/wq_failures.c
+++ b/tools/testing/selftests/bpf/progs/wq_failures.c
@@ -48,7 +48,7 @@ __log_level(2)
 __flag(BPF_F_TEST_STATE_FREQ)
 __failure
 __msg(": (85) call bpf_wq_init#") /* anchor message */
-__msg("pointer in R2 isn't map pointer")
+__msg("R2 type=fp expected=map_ptr")
 long test_wq_init_nomap(void *ctx)
 {
 	struct bpf_wq *wq;
@@ -98,7 +98,7 @@ __failure
  * is a correct bpf_wq pointer.
  */
 __msg(": (85) call bpf_wq_set_callback#") /* anchor message */
-__msg("R1 doesn't point to a map value")
+__msg("R1 type=fp expected=map_value")
 long test_wrong_wq_pointer(void *ctx)
 {
 	int key = 0;
diff --git a/tools/testing/selftests/bpf/verifier/calls.c b/tools/testing/selftests/bpf/verifier/calls.c
index eb6e3baef412a..8b94b87135bcf 100644
--- a/tools/testing/selftests/bpf/verifier/calls.c
+++ b/tools/testing/selftests/bpf/verifier/calls.c
@@ -31,7 +31,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 is fp expected STRUCT prog_test_fail1",
+	.errstr = "R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail1", 2 },
 	},
@@ -46,7 +46,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "max struct nesting depth exceeded\nR1 is fp expected STRUCT prog_test_fail2",
+	.errstr = "max struct nesting depth exceeded\nR1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail2", 2 },
 	},
@@ -61,7 +61,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 is fp expected STRUCT prog_test_fail3",
+	.errstr = "R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail3", 2 },
 	},
@@ -76,7 +76,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 expected pointer to ctx, but got fp",
+	.errstr = "R1 type=fp expected=ctx",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_pass_ctx", 2 },
 	},
@@ -152,7 +152,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "kernel function bpf_kfunc_call_memb1_release R1 expected pointer",
+	.errstr = "bpf_kfunc_call_memb1_release R1 expected pointer",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_memb_acquire", 1 },
 		{ "bpf_kfunc_call_memb1_release", 5 },
diff --git a/tools/testing/selftests/bpf/verifier/map_kptr.c b/tools/testing/selftests/bpf/verifier/map_kptr.c
index 4b39f8472f9b5..345cecc722a38 100644
--- a/tools/testing/selftests/bpf/verifier/map_kptr.c
+++ b/tools/testing/selftests/bpf/verifier/map_kptr.c
@@ -311,7 +311,7 @@
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.fixup_map_kptr = { 1 },
 	.result = REJECT,
-	.errstr = "R1 type=rcu_ptr_or_null_ expected=percpu_ptr_",
+	.errstr = "Possibly NULL pointer passed to trusted R1",
 },
 {
 	"map_kptr: ref: reject off != 0",
@@ -342,7 +342,7 @@
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.fixup_map_kptr = { 1 },
 	.result = REJECT,
-	.errstr = "invalid kptr access, R2 type=ptr_prog_test_ref_kfunc expected=ptr_prog_test_member",
+	.errstr = "R2 must have zero offset when passed to release func",
 },
 {
 	"map_kptr: ref: reference state created and released on xchg",

3/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65617
							output: 37
							thoughts: 880
						

Thoughts:
**BPF Verifier Refactoring: Worthy of Fuzzing**

Okay, let's break this down. First, the patch series. I see it's touching several key files within the BPF subsystem: `include/linux/bpf.h`, `include/linux/bpf_verifier.h`, and various core verifier and helper files like `kernel/bpf/verifier.c`, `kernel/bpf/helpers.c`, `kernel/bpf/fixups.c`, and `kernel/bpf/btf.c`. Plus, there are changes in the selftests, which is always a good sign for potential fuzzing targets.

Specifically, it seems like the BPF verifier logic is undergoing a significant refactoring and unification. I'm noting the changes to the `bpf_arg_type` enum, the merging of `check_kfunc_args` and `check_func_arg` into a unified `check_func_args`, and the updates to packet access checking. There's also mention of `bpf_do_misc_fixups` using `aux->arg_prog` and reorganized argument checks. This is the kind of deep dive that's potentially *very* interesting.

Now, considering the triage philosophy. "REACHABILITY IS THE PRIMARY GATE", which is the first, and most important filter. This modification falls squarely under that criterion. This patch is modifying core BPF verifier logic, specifically in those crucial files: `kernel/bpf/verifier.c`, `kernel/bpf/fixups.c`, `kernel/bpf/btf.c`, and `kernel/bpf/helpers.c`. BPF is a major subsystem. The `bpf()` syscall (`sys_bpf`) is a standard entry point, and it's heavily fuzzed by syzkaller. That makes this code *definitely* reachable. The point is to make sure we don't end up with a regression, or a triggered assertion.

"DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS." Refactorings *must* be tested. This is a refactoring, touching core logic. It has to be tested, particularly in the BPF space. It's a "CORE SUBSYSTEM" under the triage. And it's restructuring reachable data structures and algorithm flows.

So, *WorthFuzzing* is unequivocally `true`. This isn't optional; it's essential.

Next, I need to figure out the necessary configurations. I'll need to check the Kconfig files for these BPF components to understand the dependencies and compilation requirements. Then I can use those to verify that the path is reachable.



4/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "obj-\\$\\(CONFIG_BPF_SYSCALL\\)",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/Makefile=7=CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy)\nkernel/bpf/Makefile-8-\nkernel/bpf/Makefile:9:obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o cnum.o log.o token.o liveness.o const_fold.o diagnostics.o\nkernel/bpf/Makefile:10:obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o\nkernel/bpf/Makefile:11:obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o\nkernel/bpf/Makefile:12:obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o\nkernel/bpf/Makefile:13:obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o\nkernel/bpf/Makefile:14:obj-$(CONFIG_BPF_SYSCALL) += fixups.o cfg.o states.o backtrack.o check_btf.o\nkernel/bpf/Makefile-15-obj-${CONFIG_BPF_LSM}\t  += bpf_inode_storage.o\nkernel/bpf/Makefile:16:obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\nkernel/bpf/Makefile-17-obj-$(CONFIG_BPF_JIT) += trampoline.o\nkernel/bpf/Makefile:18:obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\nkernel/bpf/Makefile-19-ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\n--\nkernel/bpf/Makefile=23=ifeq ($(CONFIG_NET),y)\nkernel/bpf/Makefile:24:obj-$(CONFIG_BPF_SYSCALL) += devmap.o\nkernel/bpf/Makefile:25:obj-$(CONFIG_BPF_SYSCALL) += cpumap.o\nkernel/bpf/Makefile:26:obj-$(CONFIG_BPF_SYSCALL) += offload.o\nkernel/bpf/Makefile:27:obj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\nkernel/bpf/Makefile:28:obj-$(CONFIG_BPF_SYSCALL) += tcx.o\nkernel/bpf/Makefile-29-endif\nkernel/bpf/Makefile=30=ifeq ($(CONFIG_KEYS),y)\nkernel/bpf/Makefile:31:obj-$(CONFIG_BPF_SYSCALL) += keys.o\nkernel/bpf/Makefile-32-endif\nkernel/bpf/Makefile=33=ifeq ($(CONFIG_PERF_EVENTS),y)\nkernel/bpf/Makefile:34:obj-$(CONFIG_BPF_SYSCALL) += stackmap.o\nkernel/bpf/Makefile-35-endif\nkernel/bpf/Makefile=36=ifeq ($(CONFIG_CGROUPS),y)\nkernel/bpf/Makefile:37:obj-$(CONFIG_BPF_SYSCALL) += cgroup_iter.o bpf_cgrp_storage.o\nkernel/bpf/Makefile-38-endif\n--\nkernel/bpf/Makefile=40=ifeq ($(CONFIG_INET),y)\nkernel/bpf/Makefile:41:obj-$(CONFIG_BPF_SYSCALL) += reuseport_array.o\nkernel/bpf/Makefile-42-endif\n--\nkernel/bpf/Makefile=46=ifeq ($(CONFIG_BPF_JIT),y)\nkernel/bpf/Makefile:47:obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o\nkernel/bpf/Makefile:48:obj-$(CONFIG_BPF_SYSCALL) += cpumask.o\nkernel/bpf/Makefile-49-# bpf_lsm_proto.o must precede bpf_lsm.o. The current pahole logic\n--\nkernel/bpf/Makefile=61=ifneq ($(CONFIG_CRYPTO),)\nkernel/bpf/Makefile:62:obj-$(CONFIG_BPF_SYSCALL) += crypto.o\nkernel/bpf/Makefile-63-endif\nkernel/bpf/Makefile=64=obj-$(CONFIG_BPF_PRELOAD) += preload/\nkernel/bpf/Makefile-65-\nkernel/bpf/Makefile:66:obj-$(CONFIG_BPF_SYSCALL) += relo_core.o\nkernel/bpf/Makefile:67:obj-$(CONFIG_BPF_SYSCALL) += btf_iter.o\nkernel/bpf/Makefile:68:obj-$(CONFIG_BPF_SYSCALL) += btf_relocate.o\nkernel/bpf/Makefile:69:obj-$(CONFIG_BPF_SYSCALL) += kmem_cache_iter.o\nkernel/bpf/Makefile-70-ifeq ($(CONFIG_DMA_SHARED_BUFFER),y)\nkernel/bpf/Makefile:71:obj-$(CONFIG_BPF_SYSCALL) += dmabuf_iter.o\nkernel/bpf/Makefile-72-endif\n"
}

5/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 68530
							output: 31
							thoughts: 34
						

Thoughts:
Here's my thought process, as if I were considering those options in Kconfig:

**Deep Dive into BPF System Call Configuration**

Okay, time to get this straight. I need to make sure the kernel configuration for BPF is spot on. My immediate focus is on the `BPF_SYSCALL` option. That's the core, right? Without it, none of this is going to work. Let's dig in and make sure it's enabled.

Now, because I am deeply interested in debugging and analysis I also need to make sure I consider associated settings. I'm thinking of `DEBUG_INFO_BTF`. BTF data is absolutely crucial for proper symbol resolution and understanding what's happening within BPF programs. I can't be flying blind in this environment, it just slows everything down. I need to be able to analyze my eBPF code, and this is critical.

And lastly, I need to keep `BPF_JIT` in mind. I need to figure out if it's set or if there's any weirdness. That's for performance. A JIT-compiled BPF program will perform significantly better. So I need to be sure that I'm getting that advantage. The JIT is the bedrock of BPF performance, so I need to check the options.



6/2 2026/09/11 22:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config BPF_SYSCALL",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "error": "no matches"
}

7/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 68892
							output: 31
							thoughts: 17
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Tracing the BPF_SYSCALL Configuration**

Okay, so I need to track down the definition of `config BPF_SYSCALL`. My immediate approach is to locate where this preprocessor symbol is declared. Given that it's a kernel configuration option, my hunch is that it'll be in one of the kernel's Kconfig files. The challenge is narrowing down *which* Kconfig file. I'll likely start with a `find` or `grep` search within the kernel source tree, possibly limiting the search to the `Kconfig` or `Config.in` files to reduce noise. I'll need to be mindful of potential cross-references and dependencies, as this configuration option might be nested under other configurations or have conditional dependencies that influence its presence. My aim is to quickly pinpoint the file and specific section where `BPF_SYSCALL` is declared and potentially see its default value, help text, and any other relevant dependencies. From there, I can better understand its role and impact on the kernel build.



8/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "config BPF_SYSCALL",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=29=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-30-\nkernel/bpf/Kconfig:31:config BPF_SYSCALL\nkernel/bpf/Kconfig-32-\tbool \"Enable bpf() system call\"\n"
}

9/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 69259
							output: 34
							thoughts: 21
						

Thoughts:
Here's my take on those lines from `kernel/bpf/Kconfig`. Let me see...

**Analyzing Kernel BPF Configuration Options**

Okay, so I'm diving into lines 25-60 of `kernel/bpf/Kconfig`. Right, let's get down to brass tacks. I need to understand the configuration options related to BPF within the kernel. I'm looking for the key features and their dependencies, basically what knobs I can tweak and what that will actually *do* to the behavior of the system. I have to see how the various features and supporting options are structured. This seems to be where the core BPF functionality is defined, so I expect to find the essentials. Let me see... I'll have to pay attention to any intricate dependencies - these can be subtle and tricky in such a large and complex project. The goal is to develop a complete picture of the configurable landscape for BPF. I'm looking for the essential building blocks, and how they relate to the bigger picture. I'm expecting this to outline the fundamental BPF components, their purpose, and their impacts. I have to know the implications of each configuration choice. Time to get started.



10/2 2026/09/11 22:36 tool read-file
0m Args:
{
  "File": "kernel/bpf/Kconfig",
  "FirstLine": 25,
  "LineCount": 40
}

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

11/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 70154
							output: 32
							thoughts: 35
						

Thoughts:
**Investigating kfunc Support and BTF Dependency**

Okay, so I'm wondering about the interplay between `kfunc` support and the requirement for `DEBUG_INFO_BTF`. My initial thought is, does one intrinsically depend on the other? It seems plausible, but I need concrete evidence before drawing any conclusions.

Therefore, the next logical step is to pinpoint the exact definition and guarding conditions of `btf_vmlinux`. That should provide the necessary context to determine whether its inclusion is mandatory, optional, or irrelevant when it comes to supporting `kfunc`. This is a pretty standard debugging approach: get a lay of the land, then use that to determine the next step.



12/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "CONFIG_DEBUG_INFO_BTF",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/Makefile=43=ifeq ($(CONFIG_SYSFS),y)\nkernel/bpf/Makefile:44:obj-$(CONFIG_DEBUG_INFO_BTF) += sysfs_btf.o\nkernel/bpf/Makefile-45-endif\n--\nkernel/bpf/btf.c=6416=static struct btf *btf_parse_base(struct btf_verifier_env *env, const char *name,\n--\nkernel/bpf/btf.c-6421-\nkernel/bpf/btf.c:6422:\tif (!IS_ENABLED(CONFIG_DEBUG_INFO_BTF))\nkernel/bpf/btf.c-6423-\t\treturn ERR_PTR(-ENOENT);\n--\nkernel/bpf/btf.c=6502=__u32 btf_relocate_id(const struct btf *btf, __u32 id)\n--\nkernel/bpf/btf.c-6508-\nkernel/bpf/btf.c:6509:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-6510-\nkernel/bpf/btf.c=6511=static struct btf *btf_parse_module(const char *module_name, const void *data,\n--\nkernel/bpf/btf.c-6606-\nkernel/bpf/btf.c:6607:#endif /* CONFIG_DEBUG_INFO_BTF_MODULES */\nkernel/bpf/btf.c-6608-\n--\nkernel/bpf/btf.c=8524=enum {\n--\nkernel/bpf/btf.c-8527-\nkernel/bpf/btf.c:8528:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8529-struct btf_module {\n--\nkernel/bpf/btf.c=8666=fs_initcall(btf_module_init);\nkernel/bpf/btf.c:8667:#endif /* CONFIG_DEBUG_INFO_BTF_MODULES */\nkernel/bpf/btf.c-8668-\nkernel/bpf/btf.c=8669=struct module *btf_try_get_module(const struct btf *btf)\n--\nkernel/bpf/btf.c-8671-\tstruct module *res = NULL;\nkernel/bpf/btf.c:8672:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8673-\tstruct btf_module *btf_mod, *tmp;\n--\nkernel/bpf/btf.c=8699=struct btf *btf_get_module_btf(const struct module *module)\nkernel/bpf/btf.c-8700-{\nkernel/bpf/btf.c:8701:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8702-\tstruct btf_module *btf_mod, *tmp;\n--\nkernel/bpf/btf.c-8712-\nkernel/bpf/btf.c:8713:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8714-\tmutex_lock(\u0026btf_module_mutex);\n--\nkernel/bpf/btf.c=8729=static int check_btf_kconfigs(const struct module *module, const char *feature)\nkernel/bpf/btf.c-8730-{\nkernel/bpf/btf.c:8731:\tif (!module \u0026\u0026 IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {\nkernel/bpf/btf.c-8732-\t\tpr_err(\"missing vmlinux BTF, cannot register %s\\n\", feature);\n--\nkernel/bpf/btf.c-8734-\t}\nkernel/bpf/btf.c:8735:\tif (module \u0026\u0026 IS_ENABLED(CONFIG_DEBUG_INFO_BTF_MODULES))\nkernel/bpf/btf.c-8736-\t\tpr_warn(\"missing module BTF, cannot register %s\\n\", feature);\n--\nkernel/bpf/btf.c=8877=static int btf_check_kfunc_name(struct btf *btf, const char *func_name, u32 kind)\nkernel/bpf/btf.c-8878-{\nkernel/bpf/btf.c:8879:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8880-\tstruct btf_module *btf_mod, *tmp;\n--\nkernel/bpf/btf.c-8893-\nkernel/bpf/btf.c:8894:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-8895-\tguard(mutex)(\u0026btf_module_mutex);\n--\nkernel/bpf/btf.c=9542=static struct bpf_cand_cache *populate_cand_cache(struct bpf_cand_cache *cands,\n--\nkernel/bpf/btf.c-9569-\nkernel/bpf/btf.c:9570:#ifdef CONFIG_DEBUG_INFO_BTF_MODULES\nkernel/bpf/btf.c-9571-static void __purge_cand_cache(struct btf *btf, struct bpf_cand_cache **cache,\n--\nkernel/bpf/verifier.c=2849=int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)\n--\nkernel/bpf/verifier.c-2865-\t\tif (!btf_vmlinux) {\nkernel/bpf/verifier.c:2866:\t\t\tverbose(env, \"calling kernel function is not supported without CONFIG_DEBUG_INFO_BTF\\n\");\nkernel/bpf/verifier.c-2867-\t\t\treturn -ENOTSUPP;\n--\nkernel/bpf/verifier.c=6230=static int check_ptr_to_map_access(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-6244-\tif (!btf_vmlinux) {\nkernel/bpf/verifier.c:6245:\t\tverbose(env, \"map_ptr access not supported without CONFIG_DEBUG_INFO_BTF\\n\");\nkernel/bpf/verifier.c-6246-\t\treturn -ENOTSUPP;\n--\nkernel/bpf/verifier.c=18875=static int check_pseudo_btf_id(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-18891-\t\tif (!btf_vmlinux) {\nkernel/bpf/verifier.c:18892:\t\t\tverbose(env, \"kernel is missing BTF, make sure CONFIG_DEBUG_INFO_BTF=y is specified in Kconfig.\\n\");\nkernel/bpf/verifier.c-18893-\t\t\treturn -EINVAL;\n--\nkernel/bpf/verifier.c=20783=struct btf *bpf_get_btf_vmlinux(void)\n--\nkernel/bpf/verifier.c-20787-\nkernel/bpf/verifier.c:20788:\tif (!btf \u0026\u0026 IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {\nkernel/bpf/verifier.c-20789-\t\tmutex_lock(\u0026btf_vmlinux_lock);\n"
}

13/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 72319
							output: 33
							thoughts: 1350
						

Thoughts:
**Determining the Right Configuration for Kfunc Verification and BTF Matching Testing**

Okay, so I need to figure out the right configuration to effectively test the kfunc verification and BTF matching code in the verifier. `DEBUG_INFO_BTF` and `BPF_SYSCALL` are definitely crucial, no doubt about that. But are they *enough*? I need to make sure I cover everything, so I'm thinking about other potential configurations.

I see a check in `verifier.c` (line 12760) that calls `bpf_jit_supports_arena_args()`. Also, `bpf_do_misc_fixups` handles JIT and instruction fixups. Since `BPF_JIT` is generally used with BPF, should I include it in the configuration as well? Let's check!

I need to understand what parts of the code are actually touched in the verifier. I'll need to identify all relevant functions that are either modified or called by the modified code.

Here's my list of functions to check:
1.  `check_func_args`
2.  `check_func_arg`
3.  `check_func_proto`
4.  `check_func_arg_nullability`
5.  `check_func_arg_release`
6.  `check_reg_type`
7.  `resolve_func_arg_type`
8.  `process_arg_ptr_to_btf_id`
9.  `get_kfunc_arg_type`
10. `gen_kfunc_arg_proto`
11. `bpf_do_misc_fixups`
12. `check_helper_call`
13. `check_kfunc_call`
14. `may_access_direct_pkt_data`
15. `process_dynptr_func`
16. `check_helper_mem_access`
17. `ref_convert_alloc_rcu_protected`
18. `btf_check_func_arg_match`
19. `bpf_diag_arg_type_plain`

Now I need to determine if these functions are static, exported, or otherwise visible. Specifically, let's look at `verifier.c`.

I see that `check_func_args`, `check_func_arg`, `check_func_proto`, `resolve_func_arg_type`, `process_arg_ptr_to_btf_id`, `check_helper_call`, and `check_kfunc_call` are all static. `bpf_check` is the top-level verifier function, but is it ubiquitous or feature-specific?

Wait, hold on. This is important. Section 4 says I should avoid ubiquitous lifecycle hot-paths, and target specific feature logic and entrypoints. That means I need to target *specific* functions that implement the change, not just any random function.

The section also mentions that static functions in .c files *do* have symbols in vmlinux, but what about the inlined ones? So, can I check whether those static symbols are actually visible? Let's verify that `check_helper_call`, `check_kfunc_call`, `check_func_args`, and others, are symbols.

I have to understand how syzkaller targets symbols. It seems `FocusSymbols` can be any function in vmlinux. Now, I should make sure that functions like `check_helper_call`, `check_kfunc_call`, `bpf_do_misc_fixups`, `check_func_proto`, `check_func_args`, `check_func_arg` are good candidates.

I'll check `bpf_do_misc_fixups` in `fixups.c`. Oh, good! `bpf_do_misc_fixups` is non-static! This is useful. This function will be useful for me to start with!



14/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "bpf_do_misc_fixups",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/fixups.c=68=static bool bpf_is_reg64(struct bpf_prog *prog, struct bpf_insn *insn)\n--\nkernel/bpf/fixups.c-83-\nkernel/bpf/fixups.c:84:\t/* address space casts converted to BPF_ALU, see bpf_do_misc_fixups() */\nkernel/bpf/fixups.c-85-\tif (is_addr_space_cast32(prog, insn))\n--\nkernel/bpf/fixups.c=1511=static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *patch, int len)\n--\nkernel/bpf/fixups.c-1539- */\nkernel/bpf/fixups.c:1540:int bpf_do_misc_fixups(struct bpf_verifier_env *env)\nkernel/bpf/fixups.c-1541-{\n--\nkernel/bpf/verifier.c=21315=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-21544-\tif (ret == 0)\nkernel/bpf/verifier.c:21545:\t\tret = bpf_do_misc_fixups(env);\nkernel/bpf/verifier.c-21546-\n"
}

15/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 74971
							output: 32
							thoughts: 70
						

Thoughts:
**Investigating BPF Function Visibility**

Okay, so `bpf_do_misc_fixups` isn't static. That immediately piqued my interest. I need to understand its scope and how it's used. Let's see...I'll check `kernel/bpf/btf.c`. Yep, as suspected, `btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)` is also non-static. I'll verify this quickly to make sure there are no other gotchas that might be easily missed. I need to understand how these two non-static functions interact and what kind of implications that has for the BPF system.



16/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "btf_prepare_func_args",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/btf.c=7671=static int btf_check_func_type_match(struct bpf_verifier_log *log,\n--\nkernel/bpf/btf.c-7770-\t\t * Compare names of structs and report an error to the user.\nkernel/bpf/btf.c:7771:\t\t * btf_prepare_func_args() already checked that t2 struct\nkernel/bpf/btf.c:7772:\t\t * is a context type. btf_prepare_func_args() will check\nkernel/bpf/btf.c-7773-\t\t * later that t1 struct is a context type as well.\n--\nkernel/bpf/btf.c=7978=static int btf_validate_return_type(struct bpf_verifier_env *env, struct btf *btf,\n--\nkernel/bpf/btf.c-8028- */\nkernel/bpf/btf.c:8029:int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)\nkernel/bpf/btf.c-8030-{\n--\nkernel/bpf/verifier.c=10230=static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,\n--\nkernel/bpf/verifier.c-10246-\nkernel/bpf/verifier.c:10247:\tret = btf_prepare_func_args(env, subprog);\nkernel/bpf/verifier.c-10248-\tif (ret) {\n--\nkernel/bpf/verifier.c=19590=static int do_check_common(struct bpf_verifier_env *env, int subprog)\n--\nkernel/bpf/verifier.c-19630-\t\t\tverbose(env, \"Validating %s() func#%d...\\n\", sub_name, subprog);\nkernel/bpf/verifier.c:19631:\t\tret = btf_prepare_func_args(env, subprog);\nkernel/bpf/verifier.c-19632-\t\tif (ret)\n--\nkernel/bpf/verifier.c-19709-\t\tif (env-\u003eprog-\u003eaux-\u003efunc_info_aux) {\nkernel/bpf/verifier.c:19710:\t\t\tret = btf_prepare_func_args(env, 0);\nkernel/bpf/verifier.c-19711-\t\t\tif (ret || sub-\u003earg_cnt != 1 || sub-\u003eargs[0].arg_type != ARG_PTR_TO_CTX) {\n"
}

17/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 75895
							output: 35
							thoughts: 1451
						

Thoughts:
**Analyzing BPF Verifier Function Changes for Enhanced Symbol Focus**

Okay, let's break down these changes in `kernel/bpf/verifier.c`. My initial thought is to examine the function modifications and their call chains. It seems there's a significant refactoring around argument checking. `check_helper_call` and `check_kfunc_call` now both rely on a unified `check_func_args` function, which in turn calls `check_func_arg`. This `check_func_arg` is the core of the new logic, delegating to several crucial routines like `resolve_func_arg_type`, `check_func_arg_nullability`, and the various BTF-related functions.

The pattern is clear, the refactoring streamlines argument handling. I also see that `btf_check_func_arg_match` has been updated to use the same consolidated logic. Further updates include changes to `check_func_proto` and `gen_kfunc_arg_proto` to incorporate new checks related to program auxiliary data.

Now, let's pinpoint how these functions are being called. Looking at `do_check`, when `BPF_CALL` instruction is encountered, the BPF helper calls, kfunc calls, and pseudo-calls to subprograms are dispatched appropriately to their respective verification functions.

I need to confirm the visibility of these functions as symbols. I know that static functions in the kernel *are* visible, assuming they aren't inlined, and these aren't. Given the sizes of `check_helper_call`, `check_kfunc_call`, `check_func_args`, `check_func_arg`, and `btf_check_func_arg_match` they are definitely not inlined, and will be visible as symbols. Good!

Let's quickly check if these functions are considered ubiquitous, but clearly, they aren't. They are specific to BPF program verification, which is called during BPF program load via `sys_bpf`.

Alright, let's now select which specific function names to include in my analysis. The objective is to pick functions that capture the essence of the changes. The most important functions include:

-   `check_func_args`: The newly unified argument check function.
-   `check_func_arg`: The core logic function handling argument checks.
-   `check_helper_call`: The entry point for helper function calls, now using unified checks.
-   `check_kfunc_call`: The entry point for kfunc function calls, now using unified checks.
-   `bpf_do_misc_fixups`: A non-static function dealing with various fixups, which is a key part of the BPF load process.
-   `btf_check_func_arg_match`: A function that's part of pseudo-calls for subprograms, and has been updated to use unified logic.
-   `process_arg_ptr_to_btf_id`: Deals with BTF information and pointer arguments.

This seems like a good group. It represents the core refactoring and related logic.

I think `process_arg_ptr_to_btf_id` is a good candidate, given that it was recently renamed and refactored.

Finally, just a quick check on the relevant configurations. We obviously have `BPF_SYSCALL` enabled, which builds the critical source files. `DEBUG_INFO_BTF` is also necessary for vmlinux BTF.

That should be the right path to track down these changes.



18/2 2026/09/11 22:36 tool grepper
0m Args:
{
  "Expression": "config DEBUG_INFO_BTF\\b",
  "PathPrefix": "lib/Kconfig.debug"
}

Results:
{
  "Output": "lib/Kconfig.debug=378=config DEBUG_INFO_SPLIT\n--\nlib/Kconfig.debug-397-\nlib/Kconfig.debug:398:config DEBUG_INFO_BTF\nlib/Kconfig.debug-399-\tbool \"Generate BTF type information\"\n"
}

19/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 78234
							output: 34
							thoughts: 22
						

Thoughts:
Here's my take on those lines from `lib/Kconfig.debug`:

**Diving into the Debugging Configuration**

Okay, time to get my hands dirty. I need to understand what's happening with the debugging configuration, so I'm heading directly into lines 395-420 of `lib/Kconfig.debug`. I know this is where the meat of the debugging options is defined, and I want to see what's being exposed to the end-user – what switches are available to enable and control various debugging features. I'm especially interested in how granular the control is, and whether it's giving me the knobs I expect to see for my specific needs. I'm looking for things that might impact tracepoints, specific error reporting, and potentially, fine-grained control over interrupt handling. It's a quick skim to get a handle on the landscape, but I'll be drilling down into the details of any options that seem relevant to my current debugging efforts. Let's see what gems are hidden in this section.



20/2 2026/09/11 22:36 tool read-file
0m Args:
{
  "File": "lib/Kconfig.debug",
  "FirstLine": 395,
  "LineCount": 25
}

Results:
{
  "Contents": " 395:\t\t  to know about the .dwo files and include them.\n 396:\t\t  Incompatible with older versions of ccache.\n 397:\t\n 398:\tconfig DEBUG_INFO_BTF\n 399:\t\tbool \"Generate BTF type information\"\n 400:\t\tdepends on !DEBUG_INFO_SPLIT \u0026\u0026 !DEBUG_INFO_REDUCED\n 401:\t\tdepends on !GCC_PLUGIN_RANDSTRUCT || COMPILE_TEST\n 402:\t\tdepends on BPF_SYSCALL\n 403:\t\tdepends on PAHOLE_VERSION \u003e= 122\n 404:\t\t# pahole uses elfutils, which does not have support for Hexagon relocations\n 405:\t\tdepends on !HEXAGON\n 406:\t\thelp\n 407:\t\t  Generate deduplicated BTF type information from DWARF debug info.\n 408:\t\t  Turning this on requires pahole v1.22 or later, which will convert\n 409:\t\t  DWARF type info into equivalent deduplicated BTF type info.\n 410:\t\n 411:\tconfig PAHOLE_HAS_BTF_TAG\n 412:\t\tdef_bool PAHOLE_VERSION \u003e= 123\n 413:\t\tdepends on CC_IS_CLANG\n 414:\t\thelp\n 415:\t\t  Decide whether pahole emits btf_tag attributes (btf_type_tag and\n 416:\t\t  btf_decl_tag) or not. Currently only clang compiler implements\n 417:\t\t  these attributes, so make the config depend on CC_IS_CLANG.\n 418:\t\n 419:\tconfig PAHOLE_HAS_LANG_EXCLUDE\n"
}

21/2 2026/09/11 22:36 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 78955
							output: 230
							thoughts: 675
						

Thoughts:
**Triage Complete: BPF Argument Verification Refactor**

Okay, let's break this down. It looks like we're zeroing in on a patch related to BPF argument verification, and I need to assess its potential impact. My initial thought is that `DEBUG_INFO_BTF` depends on `BPF_SYSCALL`, and I need to clarify if `BPF_JIT` is relevant. While the use of `bpf_jit_supports_arena_args()` suggests it *might* be connected, my understanding is that `BPF_SYSCALL` and `DEBUG_INFO_BTF` are the base requirements for BPF fuzzing. I'll make sure to verify that this is indeed sufficient.

Now, the detailed triage:

1.  **Reachability:** The core of this work is in code that's *very* reachable. `bpf(BPF_PROG_LOAD)` leads directly to verification routines like `bpf_check()`, `do_check()`, and down to the specific functions of interest: `check_helper_call()`, `check_kfunc_call()`, `check_func_args()`, and then `bpf_do_misc_fixups()`. This is textbook BPF verification; standard, reachable, and directly callable from userspace – it doesn't need any special privileges. It's a key part of the normal BPF workflow.

2.  **Changes in Logic/Invariants:** This is where things get interesting. The patch is undertaking a major refactoring of how we handle BPF helper and kfunc argument verification. It's unifying `check_kfunc_args` and `check_func_arg` into `check_func_args`. It also modifies quite a few of the lower-level functions that check the validity of arguments, including `check_reg_type`, `check_func_arg_nullability`, `check_func_arg_release`, and `process_arg_ptr_to_btf_id`. There are also changes to packet access permissions (`may_access_direct_pkt_data`, `check_helper_mem_access`) and the behavior of `bpf_do_misc_fixups`, specifically in the context of timer callbacks and `prog->aux`. The core of memory access and sizing checks have been reorganized. Based on my experience, any refactoring in the BPF verifier is a potential minefield for soundness bugs, assertion failures, verifier issues, and of course, security vulnerabilities. It's this level of change that's drawing my attention here.

3.  **Negative Criteria:** None of the negative criteria seem to apply; this is solidly within executable core kernel code in `kernel/bpf/`.

4.  **Key Symbols:** The focus here seems to be on these *specific* functions that are central to the patch's implementation: `check_func_args`, `check_func_arg`, `check_helper_call`, `check_kfunc_call`, `bpf_do_misc_fixups`, `btf_check_func_arg_match`, and `process_arg_ptr_to_btf_id`. They're not ubiquitous; they're tied directly to this work.

5.  **Required Configs:** `BPF_SYSCALL` and `DEBUG_INFO_BTF`.

So, the configurations are correct, and the arguments all seem to make sense. This looks to be a clean triage, and I think we're done here. Let's call `set-results`.



22/2 2026/09/11 22:36 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "check_func_args",
    "check_func_arg",
    "check_helper_call",
    "check_kfunc_call",
    "bpf_do_misc_fixups",
    "btf_check_func_arg_match",
    "process_arg_ptr_to_btf_id"
  ],
  "Reasoning": "The patch modifies core BPF verifier logic, unifying argument verification for BPF helpers and kfuncs into check_func_args() and check_func_arg(). It refactors register type checking, nullability and release checks, packet data access restrictions, and instruction fixups in bpf_do_misc_fixups(). This reachable logic in kernel/bpf/ directly impacts program verification and execution during BPF_PROG_LOAD syscalls, warranting fuzzing for potential safety regressions, verifier bugs, or assertion failures.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "check_func_args",
    "check_func_arg",
    "check_helper_call",
    "check_kfunc_call",
    "bpf_do_misc_fixups",
    "btf_check_func_arg_match",
    "process_arg_ptr_to_btf_id"
  ],
  "Reasoning": "The patch modifies core BPF verifier logic, unifying argument verification for BPF helpers and kfuncs into check_func_args() and check_func_arg(). It refactors register type checking, nullability and release checks, packet data access restrictions, and instruction fixups in bpf_do_misc_fixups(). This reachable logic in kernel/bpf/ directly impacts program verification and execution during BPF_PROG_LOAD syscalls, warranting fuzzing for potential safety regressions, verifier bugs, or assertion failures.",
  "WorthFuzzing": true
}

23/1 2026/09/11 22:36 action If
2m Args:
{
  "WorthFuzzing": true
}

Results:
null

24/2 2026/09/11 22:36 agent kmsan-evaluator
2m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series refactors the BPF verifier's argument-checking infrastructure by unifying helper, kfunc, and subprog argument verification paths into common functions (check_func_args, check_func_arg, check_func_arg_nullability, check_func_arg_release, etc.) and extending enum bpf_arg_type to encompass kfunc argument types. It also updates diagnostic formatting, skb packet access tracking for kfuncs, and misc fixups for prog_aux passing.\n\nAll stack-allocated and heap-allocated data structures introduced or modified in the patch (such as struct bpf_call_arg_meta, struct bpf_func_proto, and helper/kfunc descriptor records) are explicitly initialized using memset() or field assignments prior to access. The changes do not introduce or alter any kernel data copied to user space, netlink messages, socket options, or ioctl outputs. Nor do they introduce conditional branches on uninitialized kernel variables or expose uninitialized buffer regions to reads.\n\nPotential bugs introduced by this refactoring would be verifier logic errors (such as register type mismatches, safety checks, or potential out-of-bounds packet/memory accesses in BPF programs), which are detectable by KASAN, static analysis, or standard verifier test suites. Because there are no uninitialized memory access risks or info-leaks specific to KMSAN, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 2be0c3fa03e0a3b9e475d93a3ffc7b797c2ed327
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 11 22:36:09 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index e80963971f680..d0066d744ceb9 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -874,7 +874,7 @@ enum bpf_type_flag {
 
 /* function argument constraints */
 enum bpf_arg_type {
-	ARG_DONTCARE = 0,	/* unused argument in helper function */
+	ARG_UNUSED = 0,		/* unused argument; terminates argument iteration */
 
 	/* the following constraints used to prototype
 	 * bpf_map_lookup/update/delete_elem() functions
@@ -909,6 +909,22 @@ enum bpf_arg_type {
 	ARG_PTR_TO_TIMER,	/* pointer to bpf_timer */
 	ARG_KPTR_XCHG_DEST,	/* pointer to destination that kptrs are bpf_kptr_xchg'd into */
 	ARG_PTR_TO_DYNPTR,      /* pointer to bpf_dynptr. See bpf_type_flag for dynptr type */
+
+	ARG_CONST_SCALAR,	/* scalar known at verification time */
+	ARG_CONST_MEM_SIZE,	/* ARG_MEM_SIZE that must be constant */
+	ARG_PTR_TO_ALLOC_BTF_ID,	/* pointer to an allocated object */
+	ARG_PTR_TO_REFCOUNTED_KPTR,	/* pointer to a refcounted local kptr */
+	ARG_PTR_TO_ITER,	/* pointer to an iterator */
+	ARG_PTR_TO_LIST_HEAD,	/* pointer to bpf_list_head */
+	ARG_PTR_TO_LIST_NODE,	/* pointer to bpf_list_node */
+	ARG_PTR_TO_RB_ROOT,	/* pointer to bpf_rb_root */
+	ARG_PTR_TO_RB_NODE,	/* pointer to bpf_rb_node */
+	ARG_PTR_TO_WORKQUEUE,	/* pointer to bpf_wq */
+	ARG_PTR_TO_TASK_WORK,	/* pointer to bpf_task_work */
+	ARG_PTR_TO_IRQ_FLAG,	/* pointer to saved IRQ flags on the stack */
+	ARG_PTR_TO_RES_SPIN_LOCK,	/* pointer to bpf_res_spin_lock */
+	ARG_PTR_TO_PROG_AUX,	/* pointer to the caller's bpf_prog_aux */
+	ARG_IGNORE,		/* argument the verifier does not check at all */
 	__BPF_ARG_TYPE_MAX,
 
 	/* Extended arg_types. */
@@ -1005,13 +1021,13 @@ struct bpf_func_proto {
 	};
 	union {
 		struct {
-			u32 *arg1_btf_id;
-			u32 *arg2_btf_id;
-			u32 *arg3_btf_id;
-			u32 *arg4_btf_id;
-			u32 *arg5_btf_id;
+			const u32 *arg1_btf_id;
+			const u32 *arg2_btf_id;
+			const u32 *arg3_btf_id;
+			const u32 *arg4_btf_id;
+			const u32 *arg5_btf_id;
 		};
-		u32 *arg_btf_id[MAX_BPF_FUNC_ARGS];
+		const u32 *arg_btf_id[MAX_BPF_FUNC_ARGS];
 		struct {
 			size_t arg1_size;
 			size_t arg2_size;
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 9727df5af83ab..1e7593e8d5c5d 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1591,7 +1591,7 @@ struct bpf_call_arg_meta {
 	 * verification logic
 	 *   bpf_obj_drop/bpf_percpu_obj_drop
 	 *     Record the local kptr type to be drop'd
-	 *   bpf_refcount_acquire (via KF_ARG_PTR_TO_REFCOUNTED_KPTR arg type)
+	 *   bpf_refcount_acquire (via ARG_PTR_TO_REFCOUNTED_KPTR arg type)
 	 *     Record the local kptr type to be refcount_incr'd and use
 	 *     arg_owning_ref to determine whether refcount_acquire should be
 	 *     fallible
@@ -1599,7 +1599,6 @@ struct bpf_call_arg_meta {
 	struct btf *arg_btf;
 	u32 arg_btf_id;
 	bool arg_owning_ref;
-	bool arg_prog;
 
 	struct {
 		struct btf_field *field;
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 31057c8f3a7c2..122a4101ce944 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -8244,7 +8244,7 @@ int btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
 			return -EINVAL;
 		}
 		if (btf_type_is_int(t) || btf_is_any_enum(t)) {
-			sub->args[i].arg_type = ARG_ANYTHING;
+			sub->args[i].arg_type = ARG_SCALAR;
 			continue;
 		}
 		if (!is_global)
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index 0abbbe177e317..a2cac59c66391 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -960,6 +960,35 @@ const char *bpf_diag_reg_type_plain(struct bpf_verifier_env *env, enum bpf_reg_t
 	}
 }
 
+const char *bpf_diag_arg_type_plain(enum bpf_arg_type type)
+{
+	switch (base_type(type)) {
+	case ARG_MEM_SIZE:
+	case ARG_CONST_MEM_SIZE:
+		return "an integer scalar length for this memory argument";
+	case ARG_PTR_TO_CTX:
+		return "the original program context pointer or preserve it before modifying registers";
+	case ARG_SCALAR:
+	case ARG_CONST_SCALAR:
+	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		return "an integer scalar value for this argument, not a pointer or resource object";
+	case ARG_PTR_TO_CONST_STR:
+		return "a constant string pointer that the verifier recognizes, such as a string stored in a read-only map value";
+	case ARG_PTR_TO_DYNPTR:
+		return "the address of a stack dynptr object, or use a const dynptr pointer returned by the verifier-supported path";
+	case ARG_PTR_TO_ALLOC_BTF_ID:
+		return "a pointer returned by the matching BPF object allocation path";
+	case ARG_PTR_TO_REFCOUNTED_KPTR:
+		return "an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field";
+	case ARG_PTR_TO_ITER:
+		return "the address of a stack iterator object for iterator new, next, and destroy calls";
+	case ARG_PTR_TO_IRQ_FLAG:
+		return "the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave()";
+	default:
+		return "a value with one of the accepted pointer or scalar types for this call";
+	}
+}
+
 static const char *diag_arg_ordinal(int argno)
 {
 	switch (argno) {
diff --git a/kernel/bpf/diagnostics.h b/kernel/bpf/diagnostics.h
index d1b79945008a8..a4102fb049ece 100644
--- a/kernel/bpf/diagnostics.h
+++ b/kernel/bpf/diagnostics.h
@@ -51,6 +51,7 @@ const char *bpf_diag_vfmt(struct bpf_verifier_env *env, const char *fmt, va_list
 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);
+const char *bpf_diag_arg_type_plain(enum bpf_arg_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);
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index fcf68cfb91e91..2add8001c3ec3 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -2020,7 +2020,8 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			goto next_insn;
 		}
 
-		if (insn->imm == BPF_FUNC_timer_set_callback) {
+		aux = &env->insn_aux_data[i + delta];
+		if (aux->arg_prog) {
 			/* The verifier will process callback_fn as many times as necessary
 			 * with different maps and the register states prepared by
 			 * set_timer_callback_state will be accurate.
@@ -2035,7 +2036,7 @@ int bpf_do_misc_fixups(struct bpf_verifier_env *env)
 			 *     bpf_timer_set_callback-ed will return -EINVAL.
 			 */
 			struct bpf_insn ld_addrs[2] = {
-				BPF_LD_IMM64(BPF_REG_3, (long)prog->aux),
+				BPF_LD_IMM64(aux->arg_prog, (long)prog->aux),
 			};
 
 			insn_buf[0] = ld_addrs[0];
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..051b6654e57c6 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1510,6 +1510,7 @@ static const struct bpf_func_proto bpf_timer_set_callback_proto = {
 	.ret_type	= RET_INTEGER,
 	.arg1_type	= ARG_PTR_TO_TIMER,
 	.arg2_type	= ARG_PTR_TO_FUNC,
+	.arg3_type	= ARG_PTR_TO_PROG_AUX,
 };
 
 static bool defer_timer_wq_op(void)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e79750e24808..617a277c3558c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -960,8 +960,12 @@ static enum bpf_dynptr_type dynptr_reg_type(struct bpf_verifier_env *env, struct
 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)
+	/*
+	 * ARG_PTR_TO_DYNPTR without a type flag takes any type of dynptr.
+	 * Test the flags rather than the whole arg_type, which may carry
+	 * unrelated ones such as PTR_MAYBE_NULL.
+	 */
+	if (!(arg_type & DYNPTR_TYPE_FLAG_MASK))
 		return true;
 
 	return dynptr_reg_type(env, reg) == arg_to_dynptr_type(arg_type);
@@ -4877,7 +4881,7 @@ static int check_map_access(struct bpf_verifier_env *env, struct bpf_reg_state *
 }
 
 static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
-			       const struct bpf_func_proto *fn,
+			       const struct bpf_call_arg_meta *meta,
 			       enum bpf_access_type t)
 {
 	enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
@@ -4901,10 +4905,11 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env,
 	case BPF_PROG_TYPE_LWT_XMIT:
 	case BPF_PROG_TYPE_SK_SKB:
 	case BPF_PROG_TYPE_SK_MSG:
-		if (fn)
-			return fn->pkt_access;
+		if (meta && !meta->btf && meta->func_id)
+			return meta->fn->pkt_access;
 
-		env->seen_direct_write = true;
+		if (t == BPF_WRITE)
+			env->seen_direct_write = true;
 		return true;
 
 	case BPF_PROG_TYPE_CGROUP_SOCKOPT:
@@ -5162,18 +5167,6 @@ static u32 *reg2btf_ids[__BPF_REG_TYPE_MAX] = {
 	[CONST_PTR_TO_MAP] = btf_bpf_map_id,
 };
 
-static enum bpf_reg_type lookup_reg2btf_ids(u32 ref_id)
-{
-	enum bpf_reg_type type;
-
-	for (type = 0; type < __BPF_REG_TYPE_MAX; type++) {
-		if (reg2btf_ids[type] && *reg2btf_ids[type] == ref_id)
-			return type;
-	}
-
-	return NOT_INIT;
-}
-
 static bool is_trusted_reg(struct bpf_verifier_env *env, const struct bpf_reg_state *reg)
 {
 	/* A referenced register is always trusted. */
@@ -7103,6 +7096,10 @@ static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_
 	switch (base_type(reg->type)) {
 	case PTR_TO_PACKET:
 	case PTR_TO_PACKET_META:
+		if (!may_access_direct_pkt_data(env, meta, access_type)) {
+			verbose(env, "function access to the packet is not allowed\n");
+			return -EACCES;
+		}
 		return check_packet_access(env, reg, argno, 0, access_size,
 					   zero_size_allowed);
 	case PTR_TO_MAP_KEY:
@@ -7219,7 +7216,7 @@ static int check_mem_size_reg(struct bpf_verifier_env *env,
 	 * the memory that the helper could just partially fill up.
 	 */
 	if (!tnum_is_const(size_reg->var_off))
-		meta = NULL;
+		meta->arg_raw_mem.regno = 0;
 
 	if (reg_smin(size_reg) < 0) {
 		verbose(env, "%s min value is negative, either use unsigned or 'var &= const'\n",
@@ -7663,11 +7660,11 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
 /*
  * Validate dynptr arguments for helper, kfunc and subprog.
  *
- * @dynptr is both input and output. It is populated when the argument is
- * tagged with MEM_UNINIT (i.e., the dynptr argument that will be constructed)
- * and consumed when the argument is expecting to be an initialized dynptr.
- * @parent_id is used to track the referenced parent object (e.g., file or skb in
- * qdisc program) when constructing a dynptr.
+ * @meta carries the dynptr and referenced-object state. The dynptr is populated
+ * when the argument is tagged with MEM_UNINIT (i.e., the dynptr argument that
+ * will be constructed) and consumed when the argument is expected to be an
+ * initialized dynptr. The reference tracks the parent object (e.g., file or skb
+ * in qdisc program) when constructing a dynptr.
  *
  * There are two register types representing a bpf_dynptr, one is PTR_TO_STACK
  * which points to a stack slot, and the other is CONST_PTR_TO_DYNPTR.
@@ -7684,9 +7681,8 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
  * 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, const char *call_name,
-			       enum bpf_arg_type arg_type,
-			       struct ref_obj_desc *ref_obj, struct bpf_dynptr_desc *dynptr)
+			       argno_t argno, int insn_idx, enum bpf_arg_type arg_type,
+			       struct bpf_call_arg_meta *meta)
 {
 	int spi, err = 0;
 
@@ -7695,7 +7691,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			"%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,
+			env, insn_idx, argno, meta->func_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));
@@ -7723,7 +7719,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			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.",
+				"This function 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;
 		}
@@ -7736,7 +7732,8 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 				return err;
 		}
 
-		err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx, ref_obj, dynptr);
+		err = mark_stack_slots_dynptr(env, reg, arg_type, insn_idx,
+					      &meta->ref_obj, &meta->dynptr);
 	} else /* OBJ_RELEASE and None case from above */ {
 		/* For the reg->type == PTR_TO_STACK case, bpf_dynptr is never const */
 		if (reg->type == CONST_PTR_TO_DYNPTR && (arg_type & OBJ_RELEASE)) {
@@ -7766,7 +7763,7 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			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,
+				env, insn_idx, argno, meta->func_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));
@@ -7785,11 +7782,9 @@ static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_stat
 			reg = &state->stack[spi].spilled_ptr;
 		}
 
-		if (dynptr) {
-			dynptr->type = reg->dynptr.type;
-			dynptr->id = reg->id;
-			dynptr->parent_id = reg->parent_id;
-		}
+		meta->dynptr.type = reg->dynptr.type;
+		meta->dynptr.id = reg->id;
+		meta->dynptr.parent_id = reg->parent_id;
 	}
 	return err;
 }
@@ -7853,8 +7848,8 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
 			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.");
+			"the function 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 function.");
 		return -EINVAL;
 	}
 	t = btf_type_by_id(meta->btf, btf_id);
@@ -8178,9 +8173,43 @@ static bool arg_type_is_dynptr(enum bpf_arg_type type)
 	return base_type(type) == ARG_PTR_TO_DYNPTR;
 }
 
+/*
+ * An argument that only ever takes a scalar, so a zero register passed to it
+ * is a value rather than a NULL pointer.
+ */
+static bool arg_type_is_scalar(enum bpf_arg_type type)
+{
+	switch (base_type(type)) {
+	case ARG_SCALAR:
+	case ARG_CONST_SCALAR:
+	case ARG_MEM_SIZE:
+	case ARG_MEM_SIZE_OR_ZERO:
+	case ARG_CONST_MEM_SIZE:
+	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		return true;
+	default:
+		return false;
+	}
+}
+
+/*
+ * A kfunc is named by a BTF ID, which can take the same numeric value as an
+ * enum bpf_func_id. Only test meta->func_id against a BPF_FUNC_* once the call
+ * is known to be to a helper; meta->btf is set only for a kfunc.
+ */
+static bool is_helper_call(const struct bpf_call_arg_meta *meta, enum bpf_func_id func_id)
+{
+	return !meta->btf && meta->func_id == func_id;
+}
+
+static bool is_kfunc_call(const struct bpf_call_arg_meta *meta, u32 btf_id)
+{
+	return meta->btf && meta->func_id == btf_id;
+}
+
 static int resolve_map_arg_type(struct bpf_verifier_env *env,
-				 const struct bpf_call_arg_meta *meta,
-				 enum bpf_arg_type *arg_type)
+				const struct bpf_call_arg_meta *meta,
+				enum bpf_arg_type *arg_type)
 {
 	if (!meta->map.ptr) {
 		/* kernel subsystem misconfigured verifier */
@@ -8199,7 +8228,7 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,
 		}
 		break;
 	case BPF_MAP_TYPE_BLOOM_FILTER:
-		if (meta->func_id == BPF_FUNC_map_peek_elem)
+		if (is_helper_call(meta, BPF_FUNC_map_peek_elem))
 			*arg_type = ARG_PTR_TO_MAP_VALUE;
 		break;
 	default:
@@ -8208,6 +8237,48 @@ static int resolve_map_arg_type(struct bpf_verifier_env *env,
 	return 0;
 }
 
+static int resolve_func_arg_type(struct bpf_verifier_env *env,
+				 struct bpf_reg_state *reg, u32 arg,
+				 struct bpf_call_arg_meta *meta,
+				 enum bpf_arg_type *arg_type, u32 *arg_size);
+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				     argno_t argno, enum bpf_arg_type arg_type,
+				     const struct btf *arg_btf, u32 arg_btf_id,
+				     struct bpf_call_arg_meta *meta, int insn_idx);
+static bool is_kfunc_arg_nonown_allowed(const struct btf *btf,
+					const struct btf_param *arg);
+static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
+					  const struct btf_param *arg,
+					  const char *name);
+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta);
+static bool is_bpf_obj_drop_kfunc(u32 func_id);
+static bool is_bpf_percpu_obj_drop_kfunc(u32 func_id);
+static bool is_bpf_rbtree_add_kfunc(u32 func_id);
+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta);
+static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env);
+static int process_irq_flag(struct bpf_verifier_env *env,
+			    struct bpf_reg_state *reg, argno_t argno,
+			    struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_list_head(struct bpf_verifier_env *env,
+					   struct bpf_reg_state *reg,
+					   argno_t argno,
+					   struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_rbtree_root(struct bpf_verifier_env *env,
+					     struct bpf_reg_state *reg,
+					     argno_t argno,
+					     struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_list_node(struct bpf_verifier_env *env,
+					   struct bpf_reg_state *reg,
+					   argno_t argno,
+					   struct bpf_call_arg_meta *meta);
+static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,
+					     struct bpf_reg_state *reg,
+					     argno_t argno,
+					     struct bpf_call_arg_meta *meta);
+static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env);
+
 struct bpf_reg_types {
 	const enum bpf_reg_type types[10];
 	u32 *btf_id;
@@ -8251,7 +8322,7 @@ static const struct bpf_reg_types mem_types = {
 	},
 };
 
-static const struct bpf_reg_types spin_lock_types = {
+static const struct bpf_reg_types map_value_or_alloc_obj_types = {
 	.types = {
 		PTR_TO_MAP_VALUE,
 		PTR_TO_BTF_ID | MEM_ALLOC,
@@ -8280,7 +8351,30 @@ static const struct bpf_reg_types percpu_btf_ptr_types = {
 static const struct bpf_reg_types func_ptr_types = { .types = { PTR_TO_FUNC } };
 static const struct bpf_reg_types stack_ptr_types = { .types = { PTR_TO_STACK } };
 static const struct bpf_reg_types const_str_ptr_types = { .types = { PTR_TO_MAP_VALUE } };
-static const struct bpf_reg_types timer_types = { .types = { PTR_TO_MAP_VALUE } };
+static const struct bpf_reg_types map_value_types = { .types = { PTR_TO_MAP_VALUE } };
+static const struct bpf_reg_types arena_types = {
+	.types = {
+		PTR_TO_ARENA,
+		SCALAR_VALUE,
+	}
+};
+
+static const struct bpf_reg_types alloc_obj_drop_types = {
+	.types = {
+		PTR_TO_BTF_ID | MEM_ALLOC,
+		PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU,
+	}
+};
+
+static const struct bpf_reg_types alloc_obj_types = {
+	.types = {
+		PTR_TO_BTF_ID | MEM_ALLOC,
+		PTR_TO_BTF_ID | MEM_ALLOC | MEM_RCU,
+		PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF,
+		PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU,
+	}
+};
+
 static const struct bpf_reg_types kptr_xchg_dest_types = {
 	.types = {
 		PTR_TO_MAP_VALUE,
@@ -8311,16 +8405,30 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
 #endif
 	[ARG_PTR_TO_SOCKET]		= &fullsock_types,
 	[ARG_PTR_TO_BTF_ID]		= &btf_ptr_types,
-	[ARG_PTR_TO_SPIN_LOCK]		= &spin_lock_types,
+	[ARG_PTR_TO_SPIN_LOCK]		= &map_value_or_alloc_obj_types,
 	[ARG_PTR_TO_MEM]		= &mem_types,
 	[ARG_PTR_TO_RINGBUF_MEM]	= &ringbuf_mem_types,
 	[ARG_PTR_TO_PERCPU_BTF_ID]	= &percpu_btf_ptr_types,
 	[ARG_PTR_TO_FUNC]		= &func_ptr_types,
 	[ARG_PTR_TO_STACK]		= &stack_ptr_types,
 	[ARG_PTR_TO_CONST_STR]		= &const_str_ptr_types,
-	[ARG_PTR_TO_TIMER]		= &timer_types,
+	[ARG_PTR_TO_TIMER]		= &map_value_types,
 	[ARG_KPTR_XCHG_DEST]		= &kptr_xchg_dest_types,
 	[ARG_PTR_TO_DYNPTR]		= &dynptr_types,
+	[ARG_CONST_SCALAR]		= &scalar_types,
+	[ARG_CONST_MEM_SIZE]		= &scalar_types,
+	[ARG_PTR_TO_ALLOC_BTF_ID]	= &alloc_obj_drop_types,
+	[ARG_PTR_TO_REFCOUNTED_KPTR]	= &alloc_obj_types,
+	[ARG_PTR_TO_ITER]		= &stack_ptr_types,
+	[ARG_PTR_TO_LIST_HEAD]		= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_LIST_NODE]		= &alloc_obj_types,
+	[ARG_PTR_TO_RB_ROOT]		= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_RB_NODE]		= &alloc_obj_types,
+	[ARG_PTR_TO_RES_SPIN_LOCK]	= &map_value_or_alloc_obj_types,
+	[ARG_PTR_TO_WORKQUEUE]		= &map_value_types,
+	[ARG_PTR_TO_TASK_WORK]		= &map_value_types,
+	[ARG_PTR_TO_IRQ_FLAG]		= &stack_ptr_types,
+	[ARG_PTR_TO_ARENA]		= &arena_types,
 };
 
 static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
@@ -8360,6 +8468,71 @@ __printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u
 	bpf_diag_call_arg(env, insn_idx, argno, call_name, reason, suggestion);
 }
 
+static int check_func_arg_nullability(struct bpf_verifier_env *env,
+				      struct bpf_reg_state *reg, argno_t argno,
+				      enum bpf_arg_type arg_type,
+				      struct bpf_call_arg_meta *meta, int insn_idx)
+{
+	const char *expected_type = "pointer";
+
+	if (arg_type_is_scalar(arg_type) || type_may_be_null(arg_type) ||
+	    (!bpf_register_is_null(reg) && !type_may_be_null(reg->type)))
+		return 0;
+
+	if (meta->btf) {
+		u32 arg_btf_id;
+
+		arg_btf_id = btf_params(meta->func_proto)[arg_idx_from_argno(argno)].type;
+		expected_type = bpf_diag_fmt(env, "value of type %s",
+					     bpf_diag_fmt_btf_type(env, meta->btf, arg_btf_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, meta->func_name,
+			      "Add a NULL check and make the call only on the non-NULL path.",
+			      "the pointer may be NULL, but this call requires a non-NULL %s",
+			      expected_type);
+	return -EACCES;
+}
+
+static int check_func_arg_release(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				  argno_t argno, enum bpf_arg_type arg_type,
+				  struct bpf_call_arg_meta *meta, int insn_idx)
+{
+	const char *expected_type = "pointer";
+
+	if (!arg_type_is_release(arg_type))
+		return 0;
+
+	if (arg_type_is_dynptr(arg_type) || reg_is_referenced(env, reg) ||
+	    bpf_register_is_null(reg))
+		return 0;
+
+	verbose(env, "release function %s expects referenced PTR_TO_BTF_ID passed to %s\n",
+		meta->func_name, reg_arg_name(env, argno));
+
+	if (meta->btf) {
+		const struct btf_param *btf_arg;
+		const struct btf_type *t;
+		u32 ref_id;
+
+		btf_arg = &btf_params(meta->func_proto)[arg_idx_from_argno(argno)];
+		ref_id = btf_arg->type;
+		t = btf_type_skip_modifiers(meta->btf, btf_arg->type, NULL);
+		if (btf_type_is_ptr(t))
+			btf_type_skip_modifiers(meta->btf, t->type, &ref_id);
+		expected_type = bpf_diag_fmt(env, "value of type %s",
+					     bpf_diag_fmt_btf_type(env, meta->btf, ref_id));
+	}
+
+	bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+			      bpf_diag_fmt(env, "Pass the resource-owning %s returned by the matching acquire call, or avoid the release function after ownership has already been transferred or released.",
+					   expected_type),
+			      "release functions require a value that owns a live resource returned by a matching acquire function");
+	return -EINVAL;
+}
+
 static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
 					       const enum bpf_reg_type *types, int count)
 {
@@ -8381,19 +8554,21 @@ static const char *bpf_diag_expected_reg_types(struct bpf_verifier_env *env,
 }
 
 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, const char *call_name)
+			  enum bpf_arg_type arg_type, struct bpf_call_arg_meta *meta)
 {
 	enum bpf_reg_type expected, type = reg->type;
 	const struct bpf_reg_types *compatible;
 	const char *actual, *accepted;
-	int i, j, err;
+	int i, j;
 
 	compatible = compatible_reg_types[base_type(arg_type)];
 	if (!compatible) {
 		verifier_bug(env, "unsupported arg type %d", arg_type);
 		return -EFAULT;
 	}
+	if (meta->btf && base_type(arg_type) == ARG_PTR_TO_BTF_ID &&
+	    (base_type(type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(type)]))
+		goto found;
 
 	/* ARG_PTR_TO_MEM + RDONLY is compatible with PTR_TO_MEM and PTR_TO_MEM + RDONLY,
 	 * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM and NOT with PTR_TO_MEM + RDONLY
@@ -8413,9 +8588,14 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 		type &= ~PTR_MAYBE_NULL;
 	if (base_type(arg_type) == ARG_PTR_TO_MEM)
 		type &= ~DYNPTR_TYPE_FLAG_MASK;
+	/* Allow allocated memory for kfunc ARG_PTR_TO_MEM but not helper. */
+	if (meta->btf && base_type(arg_type) == ARG_PTR_TO_MEM &&
+	    type_is_ptr_alloc_obj(type))
+		type = PTR_TO_MEM;
 
 	/* Local kptr types are allowed as the source argument of bpf_kptr_xchg */
-	if (meta->func_id == BPF_FUNC_kptr_xchg && type_is_alloc(type) && reg_from_argno(argno) == BPF_REG_2) {
+	if (is_helper_call(meta, BPF_FUNC_kptr_xchg) && type_is_alloc(type) &&
+	    reg_from_argno(argno) == BPF_REG_2) {
 		type &= ~MEM_ALLOC;
 		type &= ~MEM_PERCPU;
 	}
@@ -8435,115 +8615,13 @@ static int check_reg_type(struct bpf_verifier_env *env, struct bpf_reg_state *re
 	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.",
+	bpf_diag_call_arg_fmt(env, env->insn_idx, argno, meta->func_name,
+			      bpf_diag_fmt(env, "Pass %s.", bpf_diag_arg_type_plain(arg_type)),
 			      "it has type %s, but this argument accepts %s",
 			      actual, accepted);
 	return -EACCES;
 
 found:
-	if (base_type(reg->type) != PTR_TO_BTF_ID)
-		return 0;
-
-	if (compatible == &mem_types) {
-		if (!(arg_type & MEM_RDONLY)) {
-			verbose(env,
-				"%s() may write into memory pointed by %s type=%s\n",
-				func_id_name(meta->func_id),
-				reg_arg_name(env, argno), reg_type_str(env, reg->type));
-			return -EACCES;
-		}
-		return 0;
-	}
-
-	switch ((int)reg->type) {
-	case PTR_TO_BTF_ID:
-	case PTR_TO_BTF_ID | PTR_TRUSTED:
-	case PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL:
-	case PTR_TO_BTF_ID | MEM_RCU:
-	case PTR_TO_BTF_ID | PTR_MAYBE_NULL:
-	case PTR_TO_BTF_ID | PTR_MAYBE_NULL | MEM_RCU:
-	{
-		/* For bpf_sk_release, it needs to match against first member
-		 * 'struct sock_common', hence make an exception for it. This
-		 * allows bpf_sk_release to work for multiple socket types.
-		 */
-		bool strict_type_match = arg_type_is_release(arg_type) &&
-					 meta->func_id != BPF_FUNC_sk_release;
-
-		if (type_may_be_null(reg->type) &&
-		    (!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;
-		}
-
-		if (!arg_btf_id) {
-			if (!compatible->btf_id) {
-				verifier_bug(env, "missing arg compatible BTF ID");
-				return -EFAULT;
-			}
-			arg_btf_id = compatible->btf_id;
-		}
-
-		if (meta->func_id == BPF_FUNC_kptr_xchg) {
-			if (map_kptr_match_type(env, meta->kptr_field, reg, reg_from_argno(argno)))
-				return -EACCES;
-		} else {
-			if (arg_btf_id == BPF_PTR_POISON) {
-				verbose(env, "verifier internal error:");
-				verbose(env, "%s has non-overwritten BPF_PTR_POISON type\n",
-					reg_arg_name(env, argno));
-				return -EACCES;
-			}
-
-			err = __check_ptr_off_reg(env, reg, argno, true);
-			if (err)
-				return err;
-
-			if (!btf_struct_ids_match(&env->log, reg->btf, reg->btf_id,
-						  reg->var_off.value, btf_vmlinux, *arg_btf_id,
-						  strict_type_match, !type_is_alloc(reg->type))) {
-				verbose(env, "%s is of type %s but %s is expected\n",
-					reg_arg_name(env, argno),
-					btf_type_name(reg->btf, reg->btf_id),
-					btf_type_name(btf_vmlinux, *arg_btf_id));
-				return -EACCES;
-			}
-		}
-		break;
-	}
-	case PTR_TO_BTF_ID | MEM_ALLOC:
-	case PTR_TO_BTF_ID | MEM_PERCPU | MEM_ALLOC:
-	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:
-	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
-		if (meta->func_id != BPF_FUNC_spin_lock && meta->func_id != BPF_FUNC_spin_unlock &&
-		    meta->func_id != BPF_FUNC_kptr_xchg) {
-			verifier_bug(env, "unimplemented handling of MEM_ALLOC");
-			return -EFAULT;
-		}
-		/* Check if local kptr in src arg matches kptr in dst arg */
-		if (meta->func_id == BPF_FUNC_kptr_xchg) {
-			int regno = reg_from_argno(argno);
-
-			if (regno == BPF_REG_2 &&
-			    map_kptr_match_type(env, meta->kptr_field, reg, regno))
-				return -EACCES;
-		}
-		break;
-	case PTR_TO_BTF_ID | MEM_PERCPU:
-	case PTR_TO_BTF_ID | MEM_PERCPU | MEM_RCU:
-	case PTR_TO_BTF_ID | MEM_PERCPU | PTR_TRUSTED:
-		/* Handled by helper specific checks */
-		break;
-	default:
-		verifier_bug(env, "invalid PTR_TO_BTF_ID register for type match");
-		return -EFAULT;
-	}
 	return 0;
 }
 
@@ -8564,10 +8642,9 @@ reg_find_field_offset(const struct bpf_reg_state *reg, s32 off, u32 fields)
 	return field;
 }
 
-static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
-				    const struct bpf_reg_state *reg, argno_t argno,
-				    enum bpf_arg_type arg_type,
-				    bool btf_id_fixed_off_ok)
+static int check_func_arg_reg_off(struct bpf_verifier_env *env,
+				  const struct bpf_reg_state *reg, argno_t argno,
+				  enum bpf_arg_type arg_type)
 {
 	u32 type = reg->type;
 
@@ -8623,12 +8700,15 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF:
 	case PTR_TO_BTF_ID | MEM_ALLOC | NON_OWN_REF | MEM_RCU:
 		/* When referenced PTR_TO_BTF_ID is passed to release function,
-		 * its fixed offset must be 0. In the other cases, fixed offset
-		 * can be non-zero unless the caller requires otherwise.
-		 * var_off always must be 0 for PTR_TO_BTF_ID, hence we still
-		 * need to do checks instead of returning.
+		 * its fixed offset must be 0. bpf_refcount_acquire() returns the
+		 * pointer it was given while incrementing the refcount at the
+		 * refcount field offset, so it needs a zero offset too. In the
+		 * other cases, fixed offset can be non-zero. var_off always must
+		 * be 0 for PTR_TO_BTF_ID, hence we still need to do checks
+		 * instead of returning.
 		 */
-		return __check_ptr_off_reg(env, reg, argno, btf_id_fixed_off_ok);
+		return __check_ptr_off_reg(env, reg, argno,
+					   base_type(arg_type) != ARG_PTR_TO_REFCOUNTED_KPTR);
 	case PTR_TO_CTX:
 		/*
 		 * Allow fixed and variable offsets for syscall context, but
@@ -8636,7 +8716,7 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 		 * otherwise we may get modified ctx in tail called programs and
 		 * global subprogs (that may act as extension prog hooks).
 		 */
-		if (arg_type != ARG_PTR_TO_CTX && is_var_ctx_off_allowed(env->prog))
+		if (base_type(arg_type) != ARG_PTR_TO_CTX && is_var_ctx_off_allowed(env->prog))
 			return 0;
 		fallthrough;
 	default:
@@ -8644,13 +8724,6 @@ static int __check_func_arg_reg_off(struct bpf_verifier_env *env,
 	}
 }
 
-static int check_func_arg_reg_off(struct bpf_verifier_env *env,
-				  const struct bpf_reg_state *reg, argno_t argno,
-				  enum bpf_arg_type arg_type)
-{
-	return __check_func_arg_reg_off(env, reg, argno, arg_type, true);
-}
-
 static int check_arg_const_str(struct bpf_verifier_env *env,
 			       struct bpf_reg_state *reg, argno_t argno)
 {
@@ -8816,61 +8889,58 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			  struct bpf_call_arg_meta *meta,
 			  int insn_idx)
 {
+	const struct btf_param *btf_arg = meta->btf ? &btf_params(meta->func_proto)[arg] : NULL;
 	const struct bpf_func_proto *fn = meta->fn;
-	u32 regno = BPF_REG_1 + arg;
-	struct bpf_reg_state *reg = reg_state(env, regno);
+	struct bpf_func_state *caller = cur_func(env);
+	struct bpf_reg_state *regs = cur_regs(env);
+	argno_t argno = argno_from_arg(arg + 1);
+	struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, arg);
 	enum bpf_arg_type arg_type = fn->arg_type[arg];
-	argno_t argno = argno_from_reg(regno);
-	enum bpf_reg_type type = reg->type;
-	u32 *arg_btf_id = NULL;
+	int regno = reg_from_argno(argno);
+	u32 arg_size = arg_type & MEM_FIXED_SIZE ? fn->arg_size[arg] : 0;
 	u32 key_size;
 	int err = 0;
 
-	if (arg_type == ARG_DONTCARE)
+	if (arg_type == ARG_PTR_TO_PROG_AUX) {
+		cur_aux(env)->arg_prog = regno;
 		return 0;
+	}
 
-	err = check_reg_arg(env, regno, SRC_OP);
-	if (err)
-		return err;
+	if (arg_type == ARG_IGNORE)
+		return 0;
+
+	if (regno >= 0) {
+		err = check_reg_arg(env, regno, SRC_OP);
+		if (err)
+			return err;
+	}
 
+	/* Preserve the legacy helper behavior for privileged pointer leaks. */
 	if (arg_type == ARG_ANYTHING) {
-		if (is_pointer_value(env, regno)) {
-			verbose(env, "R%d leaks addr into helper function\n",
-				regno);
+		if (__is_pointer_value(env->allow_ptr_leaks, reg)) {
+			verbose(env, "%s leaks addr into helper function\n",
+				reg_arg_name(env, argno));
 			return -EACCES;
 		}
 		return 0;
 	}
 
-	if (type_is_pkt_pointer(type) &&
-	    !may_access_direct_pkt_data(env, fn, BPF_READ)) {
-		verbose(env, "helper access to the packet is not allowed\n");
-		return -EACCES;
-	}
-
-	if (base_type(arg_type) == ARG_PTR_TO_MAP_VALUE) {
-		err = resolve_map_arg_type(env, meta, &arg_type);
-		if (err)
-			return err;
-	}
+	err = resolve_func_arg_type(env, reg, arg, meta, &arg_type, &arg_size);
+	if (err)
+		return err;
 
 	if (bpf_register_is_null(reg) && type_may_be_null(arg_type)) {
-		/* A NULL register has a SCALAR_VALUE type, so skip
-		 * type checking.
-		 */
-		err = mark_chain_precision(env, regno);
+		err = mark_arg_precision(env, argno);
 		if (err)
 			return err;
-		goto skip_type_check;
+		return 0;
 	}
 
-	/* arg_btf_id and arg_size are in a union. */
-	if (base_type(arg_type) == ARG_PTR_TO_BTF_ID ||
-	    base_type(arg_type) == ARG_PTR_TO_SPIN_LOCK)
-		arg_btf_id = fn->arg_btf_id[arg];
+	err = check_func_arg_nullability(env, reg, argno, arg_type, meta, insn_idx);
+	if (err)
+		return err;
 
-	err = check_reg_type(env, reg, argno, arg_type, arg_btf_id, meta,
-			     func_id_name(meta->func_id));
+	err = check_reg_type(env, reg, argno, arg_type, meta);
 	if (err)
 		return err;
 
@@ -8878,22 +8948,27 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	if (err)
 		return err;
 
-skip_type_check:
-	if (arg_type_is_release(arg_type) && !arg_type_is_dynptr(arg_type) &&
-	    !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;
-	}
+	err = check_func_arg_release(env, reg, argno, arg_type, meta, insn_idx);
+	if (err)
+		return err;
 
 	if (reg_is_referenced(env, reg))
 		update_ref_obj(&meta->ref_obj, reg);
 
 	switch (base_type(arg_type)) {
+	case ARG_CONST_SCALAR:
+		err = process_const_arg(env, reg, argno, meta);
+		if (err < 0) {
+			if (err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+						      "the function requires this scalar argument to be a verifier-known constant, but %s is variable on this path",
+						      reg_arg_name(env, argno));
+			return err;
+		}
+		break;
+	case ARG_SCALAR:
+		break;
 	case ARG_CONST_MAP_PTR:
 		/* bpf_map_xxx(map_ptr) call: remember that map_ptr */
 		err = process_map_ptr_arg(env, reg, argno, meta);
@@ -8915,7 +8990,7 @@ 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, meta,
 					      NULL);
 		if (err)
 			return err;
@@ -8947,7 +9022,7 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		 * Disable raw mode for bpf_map_peek_elem() on a bloom filter. The helper reads
 		 * the value buffer as an input rather than filling it.
 		 */
-		if (meta->func_id == BPF_FUNC_map_peek_elem &&
+		if (is_helper_call(meta, BPF_FUNC_map_peek_elem) &&
 		    meta->map.ptr->map_type == BPF_MAP_TYPE_BLOOM_FILTER)
 			meta->arg_raw_mem.regno = 0;
 
@@ -8955,9 +9030,79 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 					      arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ,
 					      false, meta, NULL);
 		break;
+	case ARG_PTR_TO_BTF_ID:
+	case ARG_PTR_TO_BTF_ID_SOCK_COMMON:
+	{
+		const u32 *arg_btf_id = fn->arg_btf_id[arg];
+		const struct btf *arg_btf = meta->btf ?: btf_vmlinux;
+
+		if (!meta->btf) {
+			const struct bpf_reg_types *compatible;
+
+			if (base_type(reg->type) != PTR_TO_BTF_ID)
+				break;
+
+			if (is_helper_call(meta, BPF_FUNC_kptr_xchg))
+				return map_kptr_match_type(env, meta->kptr_field, reg, regno) ?
+				       -EACCES : 0;
+
+			if (!arg_btf_id) {
+				compatible = compatible_reg_types[base_type(arg_type)];
+				if (!compatible->btf_id) {
+					verifier_bug(env, "missing arg compatible BTF ID");
+					return -EFAULT;
+				}
+				arg_btf_id = compatible->btf_id;
+			}
+			if (arg_btf_id == BPF_PTR_POISON) {
+				verbose(env, "verifier internal error:");
+				verbose(env, "%s has non-overwritten BPF_PTR_POISON type\n",
+					reg_arg_name(env, argno));
+				return -EACCES;
+			}
+		}
+
+		if (meta->btf && (!is_trusted_reg(env, reg) ||
+				  bpf_type_has_unsafe_modifiers(reg->type))) {
+			if (!(arg_type & MEM_RCU)) {
+				const char *actual_type, *arg_name, *expected_type;
+
+				expected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);
+				verbose(env, "%s must be referenced or trusted\n",
+					reg_arg_name(env, argno));
+				arg_name = reg_arg_name(env, argno);
+				actual_type = bpf_diag_reg_type_plain(env, reg->type);
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a pointer acquired from a verifier-tracked source, or call this function only inside the required protection if it accepts RCU pointers.",
+						      "the function requires a trusted or resource-owning pointer to %s, but %s is %s",
+						      expected_type, arg_name, actual_type);
+				return -EINVAL;
+			}
+			if (!is_rcu_reg(reg)) {
+				const char *actual_type, *arg_name, *expected_type;
+
+				expected_type = bpf_diag_fmt_btf_type(env, arg_btf, *arg_btf_id);
+				verbose(env, "%s must be a rcu pointer\n",
+					reg_arg_name(env, argno));
+				arg_name = reg_arg_name(env, argno);
+				actual_type = bpf_diag_reg_type_plain(env, reg->type);
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Use this function with a pointer that is valid in an RCU read lock region.",
+						      "the function requires an RCU-protected pointer to %s, but %s is %s",
+						      expected_type, arg_name, actual_type);
+				return -EINVAL;
+			}
+		}
+
+		err = process_arg_ptr_to_btf_id(env, reg, argno, arg_type, arg_btf,
+						*arg_btf_id, meta, insn_idx);
+		if (err < 0)
+			return err;
+		break;
+	}
 	case ARG_PTR_TO_PERCPU_BTF_ID:
 		if (!reg->btf_id) {
-			verbose(env, "Helper has invalid btf_id in R%d\n", regno);
+			verbose(env, "Helper has invalid btf_id in %s\n", reg_arg_name(env, argno));
 			return -EACCES;
 		}
 		meta->ret_btf = reg->btf;
@@ -8968,11 +9113,11 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			verbose(env, "can't spin_{lock,unlock} in rbtree cb\n");
 			return -EACCES;
 		}
-		if (meta->func_id == BPF_FUNC_spin_lock) {
+		if (is_helper_call(meta, BPF_FUNC_spin_lock)) {
 			err = process_spin_lock(env, reg, argno, PROCESS_SPIN_LOCK);
 			if (err)
 				return err;
-		} else if (meta->func_id == BPF_FUNC_spin_unlock) {
+		} else if (is_helper_call(meta, BPF_FUNC_spin_unlock)) {
 			err = process_spin_lock(env, reg, argno, 0);
 			if (err)
 				return err;
@@ -8986,45 +9131,274 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 		if (err)
 			return err;
 		break;
+	case ARG_PTR_TO_CTX:
+		if (is_bpf_cast_to_kern_ctx_kfunc(meta)) {
+			err = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog));
+			if (err < 0)
+				return -EINVAL;
+			meta->ret_btf_id = err;
+		}
+		break;
+	case ARG_PTR_TO_ARENA:
+		break;
+	case ARG_PTR_TO_ALLOC_BTF_ID:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
+			if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
+				verbose(env, "%s expected for bpf_obj_drop()\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+		} else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {
+			if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) {
+				verbose(env, "%s expected for bpf_percpu_obj_drop()\n",
+					reg_arg_name(env, argno));
+				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, meta->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) {
+			meta->arg_btf = reg->btf;
+			meta->arg_btf_id = reg->btf_id;
+		}
+		break;
 	case ARG_PTR_TO_FUNC:
 		meta->subprogno = reg->subprogno;
 		break;
 	case ARG_PTR_TO_MEM:
+	{
+		enum bpf_access_type access_type;
+		bool known_memory;
+
 		/* The access to this pointer is only checked when we hit the
 		 * next is_mem_size argument below.
 		 */
-		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, NULL);
-			if (err)
-				return err;
-			if (arg_type & MEM_ALIGNED)
-				err = check_ptr_alignment(env, reg, 0, fn->arg_size[arg], true);
+		if (!(arg_type & MEM_FIXED_SIZE))
+			break;
+
+		access_type = arg_type & MEM_WRITE ? BPF_WRITE : BPF_READ;
+		if (meta->btf)
+			access_type = BPF_READ | BPF_WRITE;
+
+		err = check_mem_reg(env, reg, argno, arg_size, access_type, meta, &known_memory);
+		if (err < 0) {
+			if (known_memory)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+					"Pass memory with at least the required number of accessible bytes and suitable read or write access.",
+					"the function expects %u bytes of memory, but the verifier cannot prove that %s provides a range of that size with the required read or write access",
+					arg_size,
+					bpf_diag_reg_type_plain(env, reg->type));
+			else
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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 function expects %u bytes of memory, but it is %s and not verifier-known memory",
+					arg_size,
+					bpf_diag_reg_type_plain(env, reg->type));
+			return err;
 		}
+		if (arg_type & MEM_ALIGNED)
+			err = check_ptr_alignment(env, reg, 0, arg_size, true);
 		break;
+	}
+	case ARG_CONST_MEM_SIZE:
+		err = process_const_arg(env, reg, argno, meta);
+		if (err < 0) {
+			if (err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a compile-time constant or a value the verifier can prove is constant at this call.",
+						      "the function requires this memory size to be a verifier-known constant, but %s is variable on this path",
+						      reg_arg_name(env, argno));
+			return err;
+		}
+		fallthrough;
 	case ARG_MEM_SIZE:
-		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, 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, NULL);
-		break;
-	case ARG_PTR_TO_DYNPTR:
-		err = process_dynptr_func(env, reg, argno, insn_idx, func_id_name(meta->func_id),
-					  arg_type, &meta->ref_obj, &meta->dynptr);
+	{
+		struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, arg - 1);
+		argno_t buff_argno = argno_from_arg(arg);
+		enum bpf_mem_size_failure failure;
+		const char *buff_arg, *size_arg;
+		bool zero_size_allowed;
+		u32 access_type;
+
+		if (meta->btf && bpf_register_is_null(buff_reg))
+			break;
+
+		access_type = fn->arg_type[arg - 1] & MEM_WRITE ? BPF_WRITE : BPF_READ;
+		if (meta->btf)
+			access_type = BPF_READ | BPF_WRITE;
+
+		zero_size_allowed = meta->btf || base_type(arg_type) == ARG_MEM_SIZE_OR_ZERO;
+
+		err = check_mem_size_reg(env, buff_reg, reg, buff_argno, argno,
+					 access_type, zero_size_allowed, meta, &failure);
+		if (!err)
+			break;
+
+		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, meta->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 provide a verifier-accessible range of the requested length",
+					      size_arg, buff_arg);
+		} else if (failure == BPF_MEM_SIZE_FAIL_SIZE) {
+			if (reg_smin(reg) < 0)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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(reg));
+			else if (!zero_size_allowed && reg_umin(reg) == 0)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+					"Ensure the memory size is non-zero before this call.",
+					"the memory size in %s may be zero, but the function requires a non-zero size",
+					size_arg);
+			else
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->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(reg), BPF_MAX_VAR_SIZ);
+		}
+		break;
+	}
+	case ARG_PTR_TO_DYNPTR: {
+		if (is_bpf_dynptr_clone_kfunc(meta) &&
+		    (arg_type & MEM_UNINIT)) {
+			enum bpf_dynptr_type parent_type = meta->dynptr.type;
+
+			if (parent_type == BPF_DYNPTR_TYPE_INVALID) {
+				verifier_bug(env, "no dynptr type for parent of clone");
+				return -EFAULT;
+			}
+
+			arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
+		}
+
+		err = process_dynptr_func(env, reg, argno, insn_idx, arg_type, meta);
 		if (err)
 			return err;
 		break;
+	}
+	case ARG_PTR_TO_ITER:
+		if (is_bpf_iter_css_task_new_kfunc(meta) &&
+		    !check_css_task_iter_allowlist(env)) {
+			verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n");
+			return -EINVAL;
+		}
+		err = process_iter_arg(env, reg, argno, insn_idx, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_LIST_HEAD:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
+		    !reg_is_referenced(env, reg)) {
+			verbose(env, "allocated object must be referenced\n");
+			return -EINVAL;
+		}
+		err = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RB_ROOT:
+		if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
+		    !reg_is_referenced(env, reg)) {
+			verbose(env, "allocated object must be referenced\n");
+			return -EINVAL;
+		}
+		err = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_LIST_NODE:
+		if (!(is_kfunc_arg_nonown_allowed(meta->btf, btf_arg) &&
+		      type_is_non_owning_ref(reg->type) && !reg_is_referenced(env, reg))) {
+			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
+				verbose(env, "%s expected pointer to allocated object\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+			if (!reg_is_referenced(env, reg)) {
+				verbose(env, "allocated object must be referenced\n");
+				return -EINVAL;
+			}
+		}
+		err = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RB_NODE:
+		if (is_bpf_rbtree_add_kfunc(meta->func_id)) {
+			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
+				verbose(env, "%s expected pointer to allocated object\n",
+					reg_arg_name(env, argno));
+				return -EINVAL;
+			}
+			if (!reg_is_referenced(env, reg)) {
+				verbose(env, "allocated object must be referenced\n");
+				return -EINVAL;
+			}
+		} else {
+			if (!type_is_non_owning_ref(reg->type) &&
+			    !reg_is_referenced(env, reg)) {
+				verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n",
+					meta->func_name);
+				return -EINVAL;
+			}
+			if (in_rbtree_lock_required_cb(env)) {
+				verbose(env, "%s not allowed in rbtree cb\n", meta->func_name);
+				return -EINVAL;
+			}
+		}
+		err = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
 	case ARG_CONST_ALLOC_SIZE_OR_ZERO:
+		if (meta->btf && is_kfunc_arg_scalar_with_name(meta->btf, btf_arg,
+							       "rdonly_buf_size"))
+			meta->r0_rdonly = true;
 		err = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
-		if (err)
+		if (err < 0) {
+			if (meta->btf && err == -EINVAL)
+				bpf_diag_call_arg_fmt(env, insn_idx, argno, meta->func_name,
+						      "Pass a verifier-known constant size for this function's buffer argument.",
+						      "the function uses this argument as a return-buffer size, but %s is invalid or variable on this path",
+						      reg_arg_name(env, argno));
 			return err;
+		}
 		break;
+	case ARG_PTR_TO_REFCOUNTED_KPTR:
+	{
+		struct btf_record *rec;
+
+		if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
+			meta->arg_owning_ref = true;
+
+		rec = reg_btf_record(reg);
+		if (!rec) {
+			verifier_bug(env, "Couldn't find btf_record");
+			return -EFAULT;
+		}
+
+		if (rec->refcount_off < 0) {
+			verbose(env, "%s doesn't point to a type with bpf_refcount field\n",
+				reg_arg_name(env, argno));
+			return -EINVAL;
+		}
+
+		meta->arg_btf = reg->btf;
+		meta->arg_btf_id = reg->btf_id;
+		break;
+	}
 	case ARG_PTR_TO_CONST_STR:
 	{
 		err = check_arg_const_str(env, reg, argno);
@@ -9032,6 +9406,38 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 			return err;
 		break;
 	}
+	case ARG_PTR_TO_WORKQUEUE:
+		err = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, &meta->map);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_TASK_WORK:
+		err = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, &meta->map);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_IRQ_FLAG:
+		err = process_irq_flag(env, reg, argno, meta);
+		if (err < 0)
+			return err;
+		break;
+	case ARG_PTR_TO_RES_SPIN_LOCK:
+	{
+		int flags;
+
+		if (in_rbtree_lock_required_cb(env)) {
+			verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n");
+			return -EACCES;
+		}
+
+		flags = get_bpf_res_spin_lock_kfunc_flags(meta);
+		if (!flags)
+			return -EFAULT;
+		err = process_spin_lock(env, reg, argno, flags);
+		if (err < 0)
+			return err;
+		break;
+	}
 	case ARG_KPTR_XCHG_DEST:
 		err = process_kptr_func(env, regno, meta);
 		if (err)
@@ -9042,6 +9448,37 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
 	return err;
 }
 
+static int check_func_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
+			   int insn_idx)
+{
+	struct bpf_func_state *caller = cur_func(env);
+	const struct btf_param *args = NULL;
+	u32 arg, nargs = MAX_BPF_FUNC_REG_ARGS;
+	int err;
+
+	if (meta->btf) {
+		args = btf_params(meta->func_proto);
+		nargs = btf_type_vlen(meta->func_proto);
+	}
+
+	if (nargs > MAX_BPF_FUNC_REG_ARGS) {
+		err = check_outgoing_stack_args(env, caller, nargs, meta->func_name,
+						meta->btf, args);
+		if (err)
+			return err;
+	}
+
+	for (arg = 0; arg < nargs; arg++) {
+		if (meta->fn->arg_type[arg] == ARG_UNUSED)
+			break;
+		err = check_func_arg(env, arg, meta, insn_idx);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
 static bool may_update_sockmap(struct bpf_verifier_env *env, int func_id)
 {
 	enum bpf_attach_type eatype = env->prog->expected_attach_type;
@@ -9339,7 +9776,7 @@ static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_a
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
-		if (fn->arg_type[i] == ARG_DONTCARE)
+		if (fn->arg_type[i] == ARG_UNUSED)
 			break;
 		if (!arg_type_is_raw_mem(fn->arg_type[i]))
 			continue;
@@ -9389,7 +9826,7 @@ static bool check_btf_id_ok(const struct bpf_func_proto *fn)
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
-		if (fn->arg_type[i] == ARG_DONTCARE)
+		if (fn->arg_type[i] == ARG_UNUSED)
 			break;
 		if (base_type(fn->arg_type[i]) == ARG_PTR_TO_BTF_ID)
 			return !!fn->arg_btf_id[i];
@@ -9412,7 +9849,7 @@ static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
 		enum bpf_arg_type arg_type = fn->arg_type[i];
 
-		if (arg_type == ARG_DONTCARE)
+		if (arg_type == ARG_UNUSED)
 			break;
 		if (base_type(arg_type) != ARG_PTR_TO_MEM)
 			continue;
@@ -9430,7 +9867,7 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
 	for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
 		enum bpf_arg_type arg_type = fn->arg_type[i];
 
-		if (arg_type == ARG_DONTCARE)
+		if (arg_type == ARG_UNUSED)
 			break;
 		if (arg_type_is_release(arg_type)) {
 			if (meta->release_regno)
@@ -9442,9 +9879,42 @@ static bool check_proto_release_reg(const struct bpf_func_proto *fn, struct bpf_
 	return true;
 }
 
-static int check_func_proto(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)
+static bool check_arg_prog_aux(struct bpf_verifier_env *env,
+			       const struct bpf_func_proto *proto)
 {
-	return check_raw_mode_ok(fn, meta) &&
+	bool seen = false;
+	argno_t argno;
+	u32 i;
+
+	for (i = 0; i < ARRAY_SIZE(proto->arg_type); i++) {
+		if (proto->arg_type[i] == ARG_UNUSED)
+			break;
+		if (proto->arg_type[i] != ARG_PTR_TO_PROG_AUX)
+			continue;
+
+		if (seen) {
+			verifier_bug(env, "Only 1 prog->aux argument supported");
+			return false;
+		}
+
+		argno = argno_from_arg(i + 1);
+		if (reg_from_argno(argno) < 0) {
+			verbose(env, "%s prog->aux cannot be a stack argument\n",
+				reg_arg_name(env, argno));
+			return false;
+		}
+
+		seen = true;
+	}
+
+	return true;
+}
+
+static int check_func_proto(struct bpf_verifier_env *env, const struct bpf_func_proto *fn,
+			    struct bpf_call_arg_meta *meta)
+{
+	return check_arg_prog_aux(env, fn) &&
+	       check_raw_mode_ok(fn, meta) &&
 	       check_arg_pair_ok(fn) &&
 	       check_mem_arg_rw_flag_ok(fn) &&
 	       check_proto_release_reg(fn, meta) &&
@@ -9664,7 +10134,8 @@ static int ref_convert_alloc_rcu_protected(struct bpf_verifier_env *env, u32 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;
+			if (!type_may_be_null(reg->type))
+				reg->id = 0;
 			reg->type &= ~MEM_ALLOC;
 			reg->type |= MEM_RCU;
 			bpf_diag_mod_end(env);
@@ -9763,12 +10234,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 	struct bpf_subprog_info *sub = subprog_info(env, 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;
+	struct bpf_call_arg_meta meta;
 	u32 i;
 	int ret, err;
 
+	/* Leave btf and func_id zero: this is neither a helper nor a kfunc. */
+	memset(&meta, 0, sizeof(meta));
+	meta.func_name = bpf_subprog_name(env, subprog);
+
 	ret = btf_prepare_func_args(env, subprog);
 	if (ret) {
 		if (bpf_in_stack_arg_cnt(sub) > 0) {
@@ -9797,7 +10272,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 		struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
 		struct bpf_subprog_arg_info *arg = &sub->args[i];
 
-		if (arg->arg_type == ARG_ANYTHING) {
+		if (arg->arg_type == ARG_SCALAR) {
 			if (reg->type != SCALAR_VALUE) {
 				bpf_log(log, "%s is not a scalar\n", reg_arg_name(env, argno));
 				return -EINVAL;
@@ -9821,7 +10296,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 				return -EINVAL;
 			}
 		} else if (base_type(arg->arg_type) == ARG_PTR_TO_MEM) {
-			ret = check_func_arg_reg_off(env, reg, argno, ARG_DONTCARE);
+			ret = check_func_arg_reg_off(env, reg, argno, ARG_PTR_TO_MEM);
 			if (ret < 0)
 				return ret;
 			if (check_mem_reg(env, reg, argno, arg->mem_size, BPF_READ | BPF_WRITE, NULL,
@@ -9852,12 +10327,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog,
 				return ret;
 
 			ret = process_dynptr_func(env, reg, argno, env->insn_idx,
-						  bpf_subprog_name(env, subprog), arg->arg_type,
-						  &ref_obj, NULL);
+						  arg->arg_type, &meta);
 			if (ret)
 				return ret;
 		} else if (base_type(arg->arg_type) == ARG_PTR_TO_BTF_ID) {
-			struct bpf_call_arg_meta meta;
 			int err;
 
 			if (bpf_register_is_null(reg) && type_may_be_null(arg->arg_type)) {
@@ -9867,10 +10340,12 @@ 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,
-					     bpf_subprog_name(env, subprog));
+			err = check_reg_type(env, reg, argno, arg->arg_type, &meta);
 			err = err ?: check_func_arg_reg_off(env, reg, argno, arg->arg_type);
+			if (!err && base_type(reg->type) == PTR_TO_BTF_ID)
+				err = process_arg_ptr_to_btf_id(env, reg, argno, arg->arg_type,
+								btf_vmlinux, arg->btf_id,
+								&meta, env->insn_idx);
 			if (err)
 				return err;
 		} else {
@@ -10987,7 +11462,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 
 	memset(&meta, 0, sizeof(meta));
 
-	err = check_func_proto(fn, &meta);
+	err = check_func_proto(env, fn, &meta);
 	if (err) {
 		verifier_bug(env, "incorrect func proto %s#%d", func_id_name(func_id), func_id);
 		return err;
@@ -11008,13 +11483,11 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 		env->insn_aux_data[insn_idx].non_sleepable = true;
 
 	meta.func_id = func_id;
+	meta.func_name = func_id_name(func_id);
 	meta.fn = fn;
-	/* check args */
-	for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
-		err = check_func_arg(env, i, &meta, insn_idx);
-		if (err)
-			return err;
-	}
+	err = check_func_args(env, &meta, insn_idx);
+	if (err)
+		return err;
 
 	err = record_func_map(env, &meta, func_id, insn_idx);
 	if (err)
@@ -11853,6 +12326,50 @@ static bool btf_type_is_scalar_struct(struct bpf_verifier_env *env,
 	return btf_struct_is_composed_of(env, btf, t, BTF_MEMBER_SCALAR);
 }
 
+static int resolve_func_arg_type(struct bpf_verifier_env *env,
+				 struct bpf_reg_state *reg, u32 arg,
+				 struct bpf_call_arg_meta *meta,
+				 enum bpf_arg_type *arg_type, u32 *arg_size)
+{
+	argno_t argno = argno_from_arg(arg + 1);
+	const struct btf_param *args;
+	const struct btf_type *ref_t, *resolve_ret;
+	const struct btf *btf;
+	const char *ref_tname;
+	u32 ref_id;
+
+	if (base_type(*arg_type) == ARG_PTR_TO_MAP_VALUE)
+		return resolve_map_arg_type(env, meta, arg_type);
+
+	if (base_type(*arg_type) != ARG_PTR_TO_BTF_ID)
+		return 0;
+
+	if (!meta->btf || arg_type_is_release(*arg_type) ||
+	    base_type(reg->type) == PTR_TO_BTF_ID ||
+	    reg2btf_ids[base_type(reg->type)])
+		return 0;
+
+	args = btf_params(meta->func_proto);
+	ref_id = *meta->fn->arg_btf_id[arg];
+	btf = is_kfunc_arg_map(meta->btf, &args[arg]) ? btf_vmlinux : meta->btf;
+	ref_t = btf_type_skip_modifiers(btf, ref_id, &ref_id);
+	ref_tname = btf_name_by_offset(btf, ref_t->name_off);
+
+	if (!btf_type_is_scalar_struct(env, btf, ref_t))
+		return 0;
+
+	resolve_ret = btf_resolve_size(btf, ref_t, arg_size);
+	if (IS_ERR(resolve_ret)) {
+		verbose(env, "%s reference type('%s %s') size cannot be determined: %ld\n",
+			reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname,
+			PTR_ERR(resolve_ret));
+		return -EINVAL;
+	}
+	*arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | (*arg_type & PTR_MAYBE_NULL);
+
+	return 0;
+}
+
 static void btf_member_path_str(const struct btf *btf, const struct btf_member_path *path,
 				char *buf, size_t buf_sz)
 {
@@ -11872,34 +12389,6 @@ static void btf_member_path_str(const struct btf *btf, const struct btf_member_p
 	}
 }
 
-enum kfunc_ptr_arg_type {
-	KF_ARG_CONST_MEM_SIZE,
-	KF_ARG_MEM_SIZE,
-	KF_ARG_CONST,
-	KF_ARG_CONST_ALLOC_SIZE_OR_ZERO,
-	KF_ARG_ANYTHING,
-	KF_ARG_PTR_TO_CTX,
-	KF_ARG_PTR_TO_ALLOC_BTF_ID,    /* Allocated object */
-	KF_ARG_PTR_TO_REFCOUNTED_KPTR, /* Refcounted local kptr */
-	KF_ARG_PTR_TO_DYNPTR,
-	KF_ARG_PTR_TO_ITER,
-	KF_ARG_PTR_TO_LIST_HEAD,
-	KF_ARG_PTR_TO_LIST_NODE,
-	KF_ARG_PTR_TO_BTF_ID,	       /* Also covers reg2btf_ids conversions */
-	KF_ARG_PTR_TO_MEM,
-	KF_ARG_PTR_TO_CALLBACK,
-	KF_ARG_PTR_TO_RB_ROOT,
-	KF_ARG_PTR_TO_RB_NODE,
-	KF_ARG_PTR_TO_CONST_STR,
-	KF_ARG_CONST_MAP_PTR,
-	KF_ARG_PTR_TO_TIMER,
-	KF_ARG_PTR_TO_WORKQUEUE,
-	KF_ARG_PTR_TO_IRQ_FLAG,
-	KF_ARG_PTR_TO_RES_SPIN_LOCK,
-	KF_ARG_PTR_TO_TASK_WORK,
-	KF_ARG_PTR_TO_ARENA,
-};
-
 enum special_kfunc_type {
 	KF_bpf_obj_new_impl,
 	KF_bpf_obj_new,
@@ -11967,7 +12456,10 @@ enum special_kfunc_type {
 	KF_bpf_task_work_schedule_resume,
 	KF_bpf_arena_alloc_pages,
 	KF_bpf_arena_free_pages,
+	KF_bpf_arena_reserve_pages,
 	KF_bpf_session_is_return,
+	KF_bpf_stream_vprintk,
+	KF_bpf_stream_print_stack,
 };
 
 BTF_ID_LIST(special_kfunc_list)
@@ -12057,11 +12549,29 @@ BTF_ID(func, bpf_task_work_schedule_signal)
 BTF_ID(func, bpf_task_work_schedule_resume)
 BTF_ID(func, bpf_arena_alloc_pages)
 BTF_ID(func, bpf_arena_free_pages)
+BTF_ID(func, bpf_arena_reserve_pages)
 #ifdef CONFIG_BPF_EVENTS
 BTF_ID(func, bpf_session_is_return)
 #else
 BTF_ID_UNUSED
 #endif
+BTF_ID(func, bpf_stream_vprintk)
+BTF_ID(func, bpf_stream_print_stack)
+
+static bool is_bpf_cast_to_kern_ctx_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]);
+}
+
+static bool is_bpf_dynptr_clone_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_clone]);
+}
+
+static bool is_bpf_iter_css_task_new_kfunc(const struct bpf_call_arg_meta *meta)
+{
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_iter_css_task_new]);
+}
 
 static bool is_bpf_obj_new_kfunc(u32 func_id)
 {
@@ -12124,52 +12634,63 @@ static bool is_kfunc_ret_null(struct bpf_call_arg_meta *meta)
 
 static bool is_kfunc_bpf_rcu_read_lock(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_lock];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_lock]);
 }
 
 static bool is_kfunc_bpf_rcu_read_unlock(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_rcu_read_unlock];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_rcu_read_unlock]);
 }
 
 static bool is_kfunc_bpf_preempt_disable(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_preempt_disable];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_disable]);
 }
 
 static bool is_kfunc_bpf_preempt_enable(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_preempt_enable];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_preempt_enable]);
 }
 
 bool bpf_is_kfunc_pkt_changing(struct bpf_call_arg_meta *meta)
 {
-	return meta->func_id == special_kfunc_list[KF_bpf_xdp_pull_data];
+	return is_kfunc_call(meta, special_kfunc_list[KF_bpf_xdp_pull_data]);
 }
 
 static int
 get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
-		   const struct btf_param *args, int arg, int nargs)
+		   const struct btf_param *args, int arg, int nargs,
+		   struct bpf_func_proto *proto)
 {
-	const struct btf_type *t, *ref_t = NULL;
+	const struct btf_type *t, *ref_t = NULL, *resolve_ret;
+	const u32 *ref_id_ptr = NULL;
 	argno_t argno = argno_from_arg(arg + 1);
 	const char *ref_tname = NULL;
+	u32 ref_id, type_size;
 	int arg_type;
 
+	proto->arg_btf_id[arg] = NULL;
+
+	if (is_kfunc_arg_prog_aux(meta->btf, &args[arg]))
+		return ARG_PTR_TO_PROG_AUX;
+
+	if (is_kfunc_arg_ignore(meta->btf, &args[arg]) || is_kfunc_arg_implicit(meta, arg))
+		return ARG_IGNORE;
+
 	t = btf_type_skip_modifiers(meta->btf, args[arg].type, NULL);
 
 	/* Scalar arguments are classified from their BTF suffix/name alone. */
 	if (btf_type_is_scalar(t)) {
 		if (is_kfunc_arg_constant(meta->btf, &args[arg]))
-			return KF_ARG_CONST;
+			return ARG_CONST_SCALAR;
 		if (is_kfunc_arg_const_mem_size(meta->btf, &args[arg]))
-			return KF_ARG_CONST_MEM_SIZE;
+			return ARG_CONST_MEM_SIZE;
 		if (is_kfunc_arg_mem_size(meta->btf, &args[arg]))
-			return KF_ARG_MEM_SIZE;
+			return ARG_MEM_SIZE;
 		if (is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdonly_buf_size") ||
 		    is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdwr_buf_size"))
-			return KF_ARG_CONST_ALLOC_SIZE_OR_ZERO;
-		return KF_ARG_ANYTHING;
+			return ARG_CONST_ALLOC_SIZE_OR_ZERO;
+		return ARG_SCALAR;
 	}
 
 	if (!btf_type_is_ptr(t)) {
@@ -12177,54 +12698,69 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 			reg_arg_name(env, argno), btf_type_str(t));
 		return -EINVAL;
 	}
-	ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
+	/* Keep a pointer to the BTF field containing the resolved referent ID. */
+	ref_id_ptr = &t->type;
+	ref_t = btf_type_skip_modifiers(meta->btf, *ref_id_ptr, &ref_id);
+	while (*ref_id_ptr != ref_id)
+		ref_id_ptr = &btf_type_by_id(meta->btf, *ref_id_ptr)->type;
 	ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
 
 	/* In this function, we verify the kfunc's BTF as per the argument type,
 	 * leaving the rest of the verification with respect to the register
 	 * type to our caller. When a set of conditions hold in the BTF type of
-	 * arguments, we resolve it to a known kfunc_ptr_arg_type.
+	 * arguments, we resolve it to a known bpf_arg_type.
 	 */
-	if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_session_is_return] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_session_cookie])
-		arg_type = KF_ARG_PTR_TO_CTX;
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_is_return]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_session_cookie]))
+		arg_type = ARG_PTR_TO_CTX;
 	else if (btf_is_prog_ctx_type(&env->log, meta->btf, t, resolve_prog_type(env->prog), arg))
-		arg_type = KF_ARG_PTR_TO_CTX;
+		arg_type = ARG_PTR_TO_CTX;
 	else if (is_kfunc_arg_alloc_obj(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_ALLOC_BTF_ID;
+		arg_type = ARG_PTR_TO_ALLOC_BTF_ID;
 	else if (is_kfunc_arg_refcounted_kptr(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_REFCOUNTED_KPTR;
-	else if (is_kfunc_arg_dynptr(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_DYNPTR;
-	else if (is_kfunc_arg_iter(meta, arg, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_ITER;
+		arg_type = ARG_PTR_TO_REFCOUNTED_KPTR;
+	else if (is_kfunc_arg_dynptr(meta->btf, &args[arg])) {
+		arg_type = ARG_PTR_TO_DYNPTR;
+
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb]))
+			arg_type |= DYNPTR_TYPE_SKB;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_xdp]))
+			arg_type |= DYNPTR_TYPE_XDP;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_skb_meta]))
+			arg_type |= DYNPTR_TYPE_SKB_META;
+		else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_from_file]) ||
+			 is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_file_discard]))
+			/* OBJ_RELEASE for the latter comes from KF_RELEASE below */
+			arg_type |= DYNPTR_TYPE_FILE;
+	} else if (is_kfunc_arg_iter(meta, arg, &args[arg]))
+		arg_type = ARG_PTR_TO_ITER;
 	else if (is_kfunc_arg_list_head(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_LIST_HEAD;
+		arg_type = ARG_PTR_TO_LIST_HEAD;
 	else if (is_kfunc_arg_list_node(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_LIST_NODE;
+		arg_type = ARG_PTR_TO_LIST_NODE;
 	else if (is_kfunc_arg_rbtree_root(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RB_ROOT;
+		arg_type = ARG_PTR_TO_RB_ROOT;
 	else if (is_kfunc_arg_rbtree_node(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RB_NODE;
+		arg_type = ARG_PTR_TO_RB_NODE;
 	else if (is_kfunc_arg_const_str(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_CONST_STR;
+		arg_type = ARG_PTR_TO_CONST_STR;
 	else if (is_kfunc_arg_const_map(meta->btf, &args[arg]))
-		arg_type = KF_ARG_CONST_MAP_PTR;
+		arg_type = ARG_CONST_MAP_PTR;
 	else if (is_kfunc_arg_map(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_BTF_ID;
+		arg_type = ARG_PTR_TO_BTF_ID;
 	else if (is_kfunc_arg_wq(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_WORKQUEUE;
+		arg_type = ARG_PTR_TO_WORKQUEUE;
 	else if (is_kfunc_arg_timer(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_TIMER;
+		arg_type = ARG_PTR_TO_TIMER;
 	else if (is_kfunc_arg_task_work(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_TASK_WORK;
+		arg_type = ARG_PTR_TO_TASK_WORK;
 	else if (is_kfunc_arg_irq_flag(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_IRQ_FLAG;
+		arg_type = ARG_PTR_TO_IRQ_FLAG;
 	else if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_RES_SPIN_LOCK;
+		arg_type = ARG_PTR_TO_RES_SPIN_LOCK;
 	else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
-		arg_type = KF_ARG_PTR_TO_CALLBACK;
+		arg_type = ARG_PTR_TO_FUNC;
 	else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
 		if (!bpf_jit_supports_arena_args()) {
 			verbose(env, "JIT does not support kfunc %s() with arena pointer arguments\n",
@@ -12247,7 +12783,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 		 * whether the JIT rebases it to the arena base or preserves NULL.
 		 * The common nullable path below records that verifier property.
 		 */
-		arg_type = KF_ARG_PTR_TO_ARENA;
+		arg_type = ARG_PTR_TO_ARENA;
 	} else if (arg + 1 < nargs &&
 		 (is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
 		  is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
@@ -12257,10 +12793,10 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 				reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
 			return -EINVAL;
 		}
-		arg_type = KF_ARG_PTR_TO_MEM;
+		arg_type = ARG_PTR_TO_MEM;
 	} else if (btf_type_is_struct(ref_t))
-		/* A pointer to a struct without a size argument is classified as KF_ARG_PTR_TO_BTF_ID */
-		arg_type = KF_ARG_PTR_TO_BTF_ID;
+		/* A pointer to a struct without a size argument is classified as ARG_PTR_TO_BTF_ID */
+		arg_type = ARG_PTR_TO_BTF_ID;
 	else {
 		/*
 		 * Otherwise this is a fixed-size memory buffer supported by
@@ -12273,19 +12809,52 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 				reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
 			return -EINVAL;
 		}
-		arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
+		resolve_ret = btf_resolve_size(meta->btf, ref_t, &type_size);
+		if (IS_ERR(resolve_ret)) {
+			verbose(env,
+				"%s reference type('%s %s') size cannot be determined: %ld\n",
+				reg_arg_name(env, argno), btf_type_str(ref_t),
+				ref_tname, PTR_ERR(resolve_ret));
+			return -EINVAL;
+		}
+		proto->arg_size[arg] = type_size;
+		arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
 	}
 
+	if (is_kfunc_arg_uninit(meta->btf, &args[arg]))
+		arg_type |= MEM_UNINIT;
+
 	if (is_kfunc_arg_nullable(meta->btf, &args[arg]))
 		arg_type |= PTR_MAYBE_NULL;
 
+	/*
+	 * Only the first argument of a KF_RELEASE kfunc releases anything, and
+	 * bpf_fetch_kfunc_arg_meta() only ever records BPF_REG_1 for it.
+	 */
+	if (is_kfunc_release(meta) && arg == 0)
+		arg_type |= OBJ_RELEASE;
+
+	if (base_type(arg_type) == ARG_PTR_TO_BTF_ID) {
+		if (is_kfunc_arg_map(meta->btf, &args[arg]))
+			proto->arg_btf_id[arg] = reg2btf_ids[CONST_PTR_TO_MAP];
+		else
+			proto->arg_btf_id[arg] = ref_id_ptr;
+
+		/*
+		 * A KF_RCU kfunc accepts an RCU-protected pointer where it would
+		 * otherwise demand a referenced or trusted one. Other argument kinds
+		 * have their own provenance requirements and must not inherit MEM_RCU.
+		 */
+		if (is_kfunc_rcu(meta))
+			arg_type |= MEM_RCU;
+	}
+
 	return arg_type;
 }
 
 static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
 			       struct bpf_func_proto *proto)
 {
-	const struct btf *btf = meta->btf;
 	const struct btf_param *args;
 	u32 i, nargs;
 	int arg_type;
@@ -12304,47 +12873,38 @@ static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg
 	}
 
 	for (i = 0; i < nargs; i++) {
-		if (is_kfunc_arg_prog_aux(btf, &args[i]) ||
-		    is_kfunc_arg_ignore(btf, &args[i]) ||
-		    is_kfunc_arg_implicit(meta, i))
-			continue;
-
-		arg_type = get_kfunc_arg_type(env, meta, args, i, nargs);
+		arg_type = get_kfunc_arg_type(env, meta, args, i, nargs, proto);
 		if (arg_type < 0)
 			return arg_type;
 
 		proto->arg_type[i] = arg_type;
 	}
 
-	return 0;
+	return check_arg_prog_aux(env, proto) ? 0 : -EINVAL;
 }
 
-static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
-					struct bpf_reg_state *reg,
-					const struct btf_type *ref_t,
-					const char *ref_tname, u32 ref_id,
-					struct bpf_call_arg_meta *meta,
-					int arg, argno_t argno)
+static int process_arg_ptr_to_btf_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
+				     argno_t argno, enum bpf_arg_type arg_type,
+				     const struct btf *arg_btf, u32 arg_btf_id,
+				     struct bpf_call_arg_meta *meta, int insn_idx)
 {
-	const struct btf_type *reg_ref_t;
-	bool strict_type_match = false;
+	bool taking_projection, struct_same, strict_type_match = false;
+	const struct btf_type *arg_t, *reg_t;
+	const char *arg_tname, *reg_tname;
 	const struct btf *reg_btf;
-	const char *reg_ref_tname;
-	bool taking_projection;
-	bool struct_same;
-	u32 reg_ref_id;
+	u32 reg_btf_id;
 
 	if (base_type(reg->type) == PTR_TO_BTF_ID) {
 		reg_btf = reg->btf;
-		reg_ref_id = reg->btf_id;
+		reg_btf_id = reg->btf_id;
 	} else {
 		reg_btf = btf_vmlinux;
-		reg_ref_id = *reg2btf_ids[base_type(reg->type)];
+		reg_btf_id = *reg2btf_ids[base_type(reg->type)];
 	}
 
-	/* Enforce strict type matching for calls to kfuncs that are acquiring
-	 * or releasing a reference, or are no-cast aliases. We do _not_
-	 * enforce strict matching for kfuncs by default,
+	/*
+	 * Enforce strict type matching for arguments that release a reference,
+	 * or are no-cast aliases. We do _not_ enforce strict matching by default,
 	 * as we want to enable BPF programs to pass types that are bitwise
 	 * equivalent without forcing them to explicitly cast with something
 	 * like bpf_cast_to_kern_ctx().
@@ -12366,27 +12926,30 @@ static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
 	 * btf_struct_ids_match() to walk the struct at the 0th offset, and
 	 * resolve types.
 	 */
-	if ((is_kfunc_release(meta) && reg_is_referenced(env, reg)) ||
-	    btf_type_ids_nocast_alias(&env->log, reg_btf, reg_ref_id, meta->btf, ref_id))
+	if ((arg_type_is_release(arg_type) && !is_helper_call(meta, BPF_FUNC_sk_release)) ||
+	    (meta->btf && btf_type_ids_nocast_alias(&env->log, reg_btf, reg_btf_id,
+						    arg_btf, arg_btf_id)))
 		strict_type_match = true;
 
-	WARN_ON_ONCE(is_kfunc_release(meta) && !tnum_is_const(reg->var_off));
+	arg_t = btf_type_skip_modifiers(arg_btf, arg_btf_id, &arg_btf_id);
+	arg_tname = btf_name_by_offset(arg_btf, arg_t->name_off);
+	reg_t = btf_type_skip_modifiers(reg_btf, reg_btf_id, &reg_btf_id);
+	reg_tname = btf_name_by_offset(reg_btf, reg_t->name_off);
+
+	struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_btf_id,
+					  reg->var_off.value, arg_btf, arg_btf_id,
+					  strict_type_match, !type_is_alloc(reg->type));
 
-	reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, &reg_ref_id);
-	reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off);
-	struct_same = btf_struct_ids_match(&env->log, reg_btf, reg_ref_id, reg->var_off.value,
-					   meta->btf, ref_id, strict_type_match,
-					   !type_is_alloc(reg->type));
 	/* If kfunc is accepting a projection type (ie. __sk_buff), it cannot
 	 * actually use it -- it must cast to the underlying type. So we allow
 	 * caller to pass in the underlying type.
 	 */
-	taking_projection = btf_is_projection_of(ref_tname, reg_ref_tname);
+	taking_projection = meta->btf && btf_is_projection_of(arg_tname, reg_tname);
 	if (!taking_projection && !struct_same) {
-		verbose(env, "kernel function %s %s expected pointer to %s %s but %s has a pointer to %s %s\n",
+		verbose(env, "%s %s expected pointer to %s %s but %s has a pointer to %s %s\n",
 			meta->func_name, reg_arg_name(env, argno),
-			btf_type_str(ref_t), ref_tname, reg_arg_name(env, argno),
-			btf_type_str(reg_ref_t), reg_ref_tname);
+			btf_type_str(arg_t), arg_tname,
+			reg_arg_name(env, argno), btf_type_str(reg_t), reg_tname);
 		return -EINVAL;
 	}
 	return 0;
@@ -12398,15 +12961,15 @@ static int process_irq_flag(struct bpf_verifier_env *env, struct bpf_reg_state *
 	int err, spi, kfunc_class = IRQ_NATIVE_KFUNC;
 	bool irq_save;
 
-	if (meta->func_id == special_kfunc_list[KF_bpf_local_irq_save] ||
-	    meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) {
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_save]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave])) {
 		irq_save = true;
-		if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
 			kfunc_class = IRQ_LOCK_KFUNC;
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_local_irq_restore] ||
-		   meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_local_irq_restore]) ||
+		   is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])) {
 		irq_save = false;
-		if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
 			kfunc_class = IRQ_LOCK_KFUNC;
 	} else {
 		verifier_bug(env, "unknown irq flags kfunc");
@@ -12599,12 +13162,20 @@ static bool is_bpf_rbtree_api_kfunc(u32 btf_id)
 	       btf_id == special_kfunc_list[KF_bpf_rbtree_right];
 }
 
-static bool is_bpf_res_spin_lock_kfunc(u32 btf_id)
+static int get_bpf_res_spin_lock_kfunc_flags(const struct bpf_call_arg_meta *meta)
 {
-	return btf_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||
-	       btf_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore];
+	int flags = PROCESS_RES_LOCK;
+
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]))
+		flags |= PROCESS_SPIN_LOCK;
+	else if (!is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock]) &&
+		 !is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
+		return 0;
+	if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) ||
+	    is_kfunc_call(meta, special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]))
+		flags |= PROCESS_LOCK_IRQ;
+	return flags;
 }
 
 static bool kfunc_spin_allowed(struct bpf_verifier_env *env, s32 func_id, s16 offset)
@@ -12881,707 +13452,6 @@ static bool check_css_task_iter_allowlist(struct bpf_verifier_env *env)
 	}
 }
 
-static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
-			    int insn_idx)
-{
-	const char *func_name = meta->func_name, *ref_tname;
-	struct bpf_func_state *caller = cur_func(env);
-	struct bpf_reg_state *regs = cur_regs(env);
-	const struct btf *btf = meta->btf;
-	const struct btf_param *args;
-	struct btf_record *rec;
-	u32 i, nargs;
-	int ret;
-
-	args = (const struct btf_param *)(meta->func_proto + 1);
-	nargs = btf_type_vlen(meta->func_proto);
-
-	ret = check_outgoing_stack_args(env, caller, nargs, func_name, btf, args);
-	if (ret)
-		return ret;
-
-	/* Check that BTF function arguments match actual types that the
-	 * verifier sees.
-	 */
-	for (i = 0; i < nargs; i++) {
-		struct bpf_reg_state *reg = get_func_arg_reg(caller, regs, i);
-		const struct btf_type *t, *ref_t, *resolve_ret;
-		enum bpf_arg_type arg_type = ARG_DONTCARE;
-		argno_t argno = argno_from_arg(i + 1);
-		int regno = reg_from_argno(argno);
-		bool btf_id_fixed_off_ok = true;
-		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])) {
-			/* Reject repeated use bpf_prog_aux */
-			if (meta->arg_prog) {
-				verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc");
-				return -EFAULT;
-			}
-			if (regno < 0) {
-				verbose(env, "%s prog->aux cannot be a stack argument\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			meta->arg_prog = true;
-			cur_aux(env)->arg_prog = regno;
-			continue;
-		}
-
-		if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i))
-			continue;
-
-		t = btf_type_skip_modifiers(btf, args[i].type, NULL);
-
-		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, args[i].type);
-			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 value of type %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 (bpf_register_is_null(reg) && type_may_be_null(kf_arg_type)) {
-			ret = mark_arg_precision(env, argno);
-			if (ret)
-				return ret;
-			continue;
-		}
-
-		if (is_kfunc_arg_map(btf, &args[i])) {
-			ref_id = *reg2btf_ids[CONST_PTR_TO_MAP];
-			ref_t = btf_type_by_id(btf_vmlinux, ref_id);
-			ref_tname = btf_name_by_offset(btf, ref_t->name_off);
-		}
-
-		switch (base_type(kf_arg_type)) {
-		case KF_ARG_CONST:
-		case KF_ARG_CONST_MEM_SIZE:
-		case KF_ARG_MEM_SIZE:
-		case KF_ARG_ANYTHING:
-		case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
-		case KF_ARG_PTR_TO_ALLOC_BTF_ID:
-		case KF_ARG_PTR_TO_BTF_ID:
-		case KF_ARG_CONST_MAP_PTR:
-		case KF_ARG_PTR_TO_ITER:
-		case KF_ARG_PTR_TO_LIST_HEAD:
-		case KF_ARG_PTR_TO_LIST_NODE:
-		case KF_ARG_PTR_TO_RB_ROOT:
-		case KF_ARG_PTR_TO_RB_NODE:
-		case KF_ARG_PTR_TO_MEM:
-		case KF_ARG_PTR_TO_CALLBACK:
-		case KF_ARG_PTR_TO_CONST_STR:
-		case KF_ARG_PTR_TO_WORKQUEUE:
-		case KF_ARG_PTR_TO_TIMER:
-		case KF_ARG_PTR_TO_TASK_WORK:
-		case KF_ARG_PTR_TO_IRQ_FLAG:
-		case KF_ARG_PTR_TO_RES_SPIN_LOCK:
-		case KF_ARG_PTR_TO_ARENA:
-			break;
-		case KF_ARG_PTR_TO_DYNPTR:
-			arg_type = ARG_PTR_TO_DYNPTR;
-			break;
-		case KF_ARG_PTR_TO_CTX:
-			arg_type = ARG_PTR_TO_CTX;
-			break;
-		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
-			arg_type = ARG_PTR_TO_BTF_ID;
-			btf_id_fixed_off_ok = false;
-			break;
-		default:
-			verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type);
-			return -EFAULT;
-		}
-
-		if (regno == meta->release_regno)
-			arg_type |= OBJ_RELEASE;
-		ret = __check_func_arg_reg_off(env, reg, argno, arg_type,
-					       btf_id_fixed_off_ok);
-		if (ret < 0)
-			return ret;
-
-		switch (base_type(kf_arg_type)) {
-		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 == -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 == -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;
-			}
-
-			if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {
-				ret = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog));
-				if (ret < 0)
-					return -EINVAL;
-				meta->ret_btf_id  = ret;
-			}
-			break;
-		case KF_ARG_PTR_TO_ARENA:
-			if (reg->type != PTR_TO_ARENA && reg->type != SCALAR_VALUE) {
-				verbose(env, "%s is not a pointer to arena or scalar\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			break;
-		case KF_ARG_PTR_TO_ALLOC_BTF_ID:
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				if (!is_bpf_obj_drop_kfunc(meta->func_id)) {
-					verbose(env, "%s expected for bpf_obj_drop()\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-			} else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {
-				if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) {
-					verbose(env, "%s expected for bpf_percpu_obj_drop()\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-			} 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) {
-				meta->arg_btf = reg->btf;
-				meta->arg_btf_id = reg->btf_id;
-			}
-			break;
-		case KF_ARG_PTR_TO_DYNPTR:
-		{
-			enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;
-
-			if (is_kfunc_arg_uninit(btf, &args[i]))
-				dynptr_arg_type |= MEM_UNINIT;
-
-			if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) {
-				dynptr_arg_type |= DYNPTR_TYPE_SKB;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) {
-				dynptr_arg_type |= DYNPTR_TYPE_XDP;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) {
-				dynptr_arg_type |= DYNPTR_TYPE_SKB_META;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {
-				dynptr_arg_type |= DYNPTR_TYPE_FILE;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {
-				dynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;
-			} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] &&
-				   (dynptr_arg_type & MEM_UNINIT)) {
-				enum bpf_dynptr_type parent_type = meta->dynptr.type;
-
-				if (parent_type == BPF_DYNPTR_TYPE_INVALID) {
-					verifier_bug(env, "no dynptr type for parent of clone");
-					return -EFAULT;
-				}
-
-				dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type);
-			}
-
-			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;
-		}
-		case KF_ARG_PTR_TO_ITER:
-			if (meta->func_id == special_kfunc_list[KF_bpf_iter_css_task_new]) {
-				if (!check_css_task_iter_allowlist(env)) {
-					verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n");
-					return -EINVAL;
-				}
-			}
-			ret = process_iter_arg(env, reg, argno, insn_idx, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_LIST_HEAD:
-			if (reg->type != PTR_TO_MAP_VALUE &&
-			    reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
-			    !reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-			ret = process_kf_arg_ptr_to_list_head(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RB_ROOT:
-			if (reg->type != PTR_TO_MAP_VALUE &&
-			    reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) &&
-			    !reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-			ret = process_kf_arg_ptr_to_rbtree_root(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_LIST_NODE:
-			if (is_kfunc_arg_nonown_allowed(btf, &args[i]) &&
-			    type_is_non_owning_ref(reg->type) && !reg_is_referenced(env, reg)) {
-				/* Allow bpf_list_front/back return value for
-				 * __nonown_allowed list-node arguments.
-				 */
-				goto check_ok;
-			}
-			if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s expected pointer to allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			if (!reg_is_referenced(env, reg)) {
-				verbose(env, "allocated object must be referenced\n");
-				return -EINVAL;
-			}
-check_ok:
-			ret = process_kf_arg_ptr_to_list_node(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RB_NODE:
-			if (is_bpf_rbtree_add_kfunc(meta->func_id)) {
-				if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-					verbose(env, "%s expected pointer to allocated object\n",
-						reg_arg_name(env, argno));
-					return -EINVAL;
-				}
-				if (!reg_is_referenced(env, reg)) {
-					verbose(env, "allocated object must be referenced\n");
-					return -EINVAL;
-				}
-			} else {
-				if (!type_is_non_owning_ref(reg->type) &&
-				    !reg_is_referenced(env, reg)) {
-					verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n", func_name);
-					return -EINVAL;
-				}
-				if (in_rbtree_lock_required_cb(env)) {
-					verbose(env, "%s not allowed in rbtree cb\n", func_name);
-					return -EINVAL;
-				}
-			}
-
-			ret = process_kf_arg_ptr_to_rbtree_node(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_CONST_MAP_PTR:
-			if (base_type(reg->type) != CONST_PTR_TO_MAP ||
-			    type_may_be_null(reg->type)) {
-				verbose(env, "pointer in %s isn't map pointer\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = process_map_ptr_arg(env, reg, argno, meta);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_BTF_ID:
-			/* Only base_type is checked, further checks are done here */
-			if (base_type(reg->type) == PTR_TO_BTF_ID ||
-			    reg2btf_ids[base_type(reg->type)]) {
-				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;
-					}
-				}
-
-				ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i, argno);
-				if (ret < 0)
-					return ret;
-				break;
-			}
-
-			if (!btf_type_is_scalar_struct(env, meta->btf, ref_t)) {
-				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),
-					btf_type_str(ref_t), ref_tname);
-				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;
-			}
-
-			/*
-			 * If the register does not contain btf id but the argument type is a pointer to
-			 * scalar-only struct, allow verifying it as a fixed size memory.
-			 */
-			kf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;
-			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",
-						reg_arg_name(env, argno), btf_type_str(ref_t),
-						ref_tname, PTR_ERR(resolve_ret));
-					return -EINVAL;
-				}
-				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:
-			ret = process_const_arg(env, reg, argno, meta);
-			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 memory size to be a verifier-known constant, but %s is variable on this path",
-							      reg_arg_name(env, argno));
-				return ret;
-			}
-			fallthrough;
-		case KF_ARG_MEM_SIZE:
-		{
-			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;
-			}
-
-			if (bpf_register_is_null(buff_reg))
-				break;
-
-			ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
-						 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;
-		}
-		case KF_ARG_PTR_TO_CALLBACK:
-			if (reg->type != PTR_TO_FUNC) {
-				verbose(env, "%s expected pointer to func\n", reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			meta->subprogno = reg->subprogno;
-			break;
-		case KF_ARG_PTR_TO_REFCOUNTED_KPTR:
-			if (!type_is_ptr_alloc_obj(reg->type)) {
-				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 an owning or non-owning pointer to a BPF-managed object containing a bpf_refcount field.",
-						      "the kfunc expects a pointer to a BPF-managed refcounted object, but %s is %s",
-						      reg_arg_name(env, argno),
-						      bpf_diag_reg_type_plain(env, reg->type));
-				return -EINVAL;
-			}
-			if (!type_is_non_owning_ref(reg->type) && reg_is_referenced(env, reg))
-				meta->arg_owning_ref = true;
-
-			rec = reg_btf_record(reg);
-			if (!rec) {
-				verifier_bug(env, "Couldn't find btf_record");
-				return -EFAULT;
-			}
-
-			if (rec->refcount_off < 0) {
-				verbose(env, "%s doesn't point to a type with bpf_refcount field\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-
-			meta->arg_btf = reg->btf;
-			meta->arg_btf_id = reg->btf_id;
-			break;
-		case KF_ARG_PTR_TO_CONST_STR:
-			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);
-			if (ret)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_WORKQUEUE:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = check_map_field_pointer(env, reg, argno, BPF_WORKQUEUE, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_TIMER:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = process_timer_func(env, reg, argno, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_TASK_WORK:
-			if (reg->type != PTR_TO_MAP_VALUE) {
-				verbose(env, "%s doesn't point to a map value\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-			ret = check_map_field_pointer(env, reg, argno, BPF_TASK_WORK, &meta->map);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_IRQ_FLAG:
-			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);
-			if (ret < 0)
-				return ret;
-			break;
-		case KF_ARG_PTR_TO_RES_SPIN_LOCK:
-		{
-			int flags = PROCESS_RES_LOCK;
-
-			if (in_rbtree_lock_required_cb(env)) {
-				verbose(env, "can't res_spin_{lock,unlock} in rbtree cb\n");
-				return -EACCES;
-			}
-
-			if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) {
-				verbose(env, "%s doesn't point to map value or allocated object\n",
-					reg_arg_name(env, argno));
-				return -EINVAL;
-			}
-
-			if (!is_bpf_res_spin_lock_kfunc(meta->func_id))
-				return -EFAULT;
-			if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock] ||
-			    meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])
-				flags |= PROCESS_SPIN_LOCK;
-			if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] ||
-			    meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore])
-				flags |= PROCESS_LOCK_IRQ;
-			ret = process_spin_lock(env, reg, argno, flags);
-			if (ret < 0)
-				return ret;
-			break;
-		}
-		}
-	}
-
-	return 0;
-}
-
 int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,
 			     s32 func_id,
 			     s16 offset,
@@ -13912,12 +13782,12 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 		struct btf_field *field = meta->arg_rbtree_root.field;
 
 		mark_reg_graph_node(regs, BPF_REG_0, &field->graph_root);
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_cast_to_kern_ctx])) {
 		mark_reg_known_zero(env, regs, BPF_REG_0);
 		regs[BPF_REG_0].type = PTR_TO_BTF_ID | PTR_TRUSTED;
 		regs[BPF_REG_0].btf = desc_btf;
 		regs[BPF_REG_0].btf_id = meta->ret_btf_id;
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_rdonly_cast])) {
 		ret_t = btf_type_by_id(desc_btf, meta->arg_constant.value);
 		if (!ret_t) {
 			verbose(env, "Unknown type ID %lld passed to kfunc bpf_rdonly_cast\n",
@@ -13937,8 +13807,8 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 				"kfunc bpf_rdonly_cast type ID argument must be of a struct or void\n");
 			return -EINVAL;
 		}
-	} else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice] ||
-		   meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice_rdwr]) {
+	} else if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice]) ||
+		   is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice_rdwr])) {
 		enum bpf_type_flag type_flag = get_dynptr_type_flag(meta->dynptr.type);
 
 		mark_reg_known_zero(env, regs, BPF_REG_0);
@@ -13953,7 +13823,7 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
 		/* PTR_MAYBE_NULL will be added when is_kfunc_ret_null is checked */
 		regs[BPF_REG_0].type = PTR_TO_MEM | type_flag;
 
-		if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_slice]) {
+		if (is_kfunc_call(meta, special_kfunc_list[KF_bpf_dynptr_slice])) {
 			regs[BPF_REG_0].type |= MEM_RDONLY;
 		} else {
 			/* this will set env->seen_direct_write to true */
@@ -14073,7 +13943,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		insn_aux->non_sleepable = true;
 
 	/* Check the arguments */
-	err = check_kfunc_args(env, &meta, insn_idx);
+	err = check_func_args(env, &meta, insn_idx);
 	if (err < 0)
 		return err;
 
@@ -17972,7 +17842,7 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,
 		cs->is_void = fn->ret_type == RET_VOID;
 		cs->num_params = 0;
 		for (i = 0; i < ARRAY_SIZE(fn->arg_type); ++i) {
-			if (fn->arg_type[i] == ARG_DONTCARE)
+			if (fn->arg_type[i] == ARG_UNUSED)
 				break;
 			cs->num_params++;
 		}
@@ -19776,7 +19646,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 			}
 
 			/* Also ensure the callback only has a single scalar argument. */
-			if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_ANYTHING) {
+			if (sub->arg_cnt != 1 || sub->args[0].arg_type != ARG_SCALAR) {
 				verbose(env, "exception cb only supports single integer argument\n");
 				ret = -EINVAL;
 				goto out;
@@ -19789,7 +19659,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 			if (arg->arg_type == ARG_PTR_TO_CTX) {
 				reg->type = PTR_TO_CTX;
 				mark_reg_known_zero(env, regs, i);
-			} else if (arg->arg_type == ARG_ANYTHING) {
+			} else if (arg->arg_type == ARG_SCALAR) {
 				reg->type = SCALAR_VALUE;
 				mark_reg_unknown(env, regs, i);
 			} else if (arg->arg_type == ARG_PTR_TO_DYNPTR) {
diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
index 14d4c1793aed5..d74a9db54c9a3 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_nf.c
@@ -13,13 +13,13 @@ struct {
 	const char *prog_name;
 	const char *err_msg;
 } test_bpf_nf_fail_tests[] = {
-	{ "alloc_release", "kernel function bpf_ct_release R1 expected pointer to STRUCT nf_conn but" },
-	{ "insert_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "lookup_insert", "kernel function bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "set_timeout_after_insert", "kernel function bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "set_status_after_insert", "kernel function bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but" },
-	{ "change_timeout_after_alloc", "kernel function bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but" },
-	{ "change_status_after_alloc", "kernel function bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but" },
+	{ "alloc_release", "bpf_ct_release R1 expected pointer to STRUCT nf_conn but" },
+	{ "insert_insert", "bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "lookup_insert", "bpf_ct_insert_entry R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "set_timeout_after_insert", "bpf_ct_set_timeout R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "set_status_after_insert", "bpf_ct_set_status R1 expected pointer to STRUCT nf_conn___init but" },
+	{ "change_timeout_after_alloc", "bpf_ct_change_timeout R1 expected pointer to STRUCT nf_conn but" },
+	{ "change_status_after_alloc", "bpf_ct_change_status R1 expected pointer to STRUCT nf_conn but" },
 	{ "write_not_allowlisted_field", "no write support to nf_conn at off" },
 	{ "lookup_null_bpf_tuple", "Possibly NULL pointer passed to trusted R2" },
 	{ "lookup_null_bpf_opts", "Possibly NULL pointer passed to trusted R4" },
diff --git a/tools/testing/selftests/bpf/prog_tests/cb_refs.c b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
index 78566b817fd70..c32c6dab49bce 100644
--- a/tools/testing/selftests/bpf/prog_tests/cb_refs.c
+++ b/tools/testing/selftests/bpf/prog_tests/cb_refs.c
@@ -11,8 +11,8 @@ struct {
 	const char *prog_name;
 	const char *err_msg;
 } cb_refs_tests[] = {
-	{ "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
-	{ "leak_prog", "Possibly NULL pointer passed to helper R2" },
+	{ "underflow_prog", "R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_" },
+	{ "leak_prog", "Unreleased reference id=4 alloc_insn=3" }, /* alloc_insn=3{2,3} */
 	{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
 	{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */
 };
diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
index 2b39cc1b09f9a..0063e60d6f2f5 100644
--- a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
@@ -70,7 +70,7 @@ static struct kfunc_test_params kfunc_tests[] = {
 	TC_FAIL(kfunc_call_test_get_mem_fail_oversized, 0, "allocation size exceeds u32 max"),
 	TC_FAIL(kfunc_call_test_get_mem_fail_not_const, 0, "is not a const"),
 	TC_FAIL(kfunc_call_test_mem_acquire_fail, 0, "acquire kernel function does not return PTR_TO_BTF_ID"),
-	TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "R1 expected pointer to ctx, but got scalar"),
+	TC_FAIL(kfunc_call_test_pointer_arg_type_mismatch, 0, "R1 type=scalar expected=ctx"),
 	TC_FAIL(kfunc_call_test_spin_lock_unsafe, 0, "function calls are not allowed while holding a lock"),
 
 	/* success cases */
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index f7f94ccebce27..b973814482481 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -54,6 +54,7 @@
 #include "verifier_iterating_callbacks.skel.h"
 #include "verifier_jeq_infer_not_null.skel.h"
 #include "verifier_jit_convergence.skel.h"
+#include "verifier_kfunc_packet_access.skel.h"
 #include "verifier_ld_ind.skel.h"
 #include "verifier_ldsx.skel.h"
 #include "verifier_leak_ptr.skel.h"
@@ -218,6 +219,7 @@ void test_verifier_int_ptr(void)              { RUN(verifier_int_ptr); }
 void test_verifier_iterating_callbacks(void)  { RUN(verifier_iterating_callbacks); }
 void test_verifier_jeq_infer_not_null(void)   { RUN(verifier_jeq_infer_not_null); }
 void test_verifier_jit_convergence(void)      { RUN(verifier_jit_convergence); }
+void test_verifier_kfunc_packet_access(void)  { RUN_TESTS(verifier_kfunc_packet_access); }
 void test_verifier_load_acquire(void)         { RUN(verifier_load_acquire); }
 void test_verifier_ld_ind(void)               { RUN(verifier_ld_ind); }
 void test_verifier_ldsx(void)                  { RUN(verifier_ldsx); }
diff --git a/tools/testing/selftests/bpf/progs/arena_kfunc.c b/tools/testing/selftests/bpf/progs/arena_kfunc.c
index 50609f3b0564a..6578cf12fa27d 100644
--- a/tools/testing/selftests/bpf/progs/arena_kfunc.c
+++ b/tools/testing/selftests/bpf/progs/arena_kfunc.c
@@ -205,7 +205,7 @@ int arena_arg_no_arena(void *ctx)
 SEC("syscall")
 __arch_x86_64
 __arch_arm64
-__failure __msg("is not a pointer to arena or scalar")
+__failure __msg("R1 type=fp expected=arena, scalar")
 int arena_arg_bad_reg(void *ctx)
 {
 	u64 buf = 0;
diff --git a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
index efe7bcae70f85..ede6a17d7da30 100644
--- a/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/cgrp_kfunc_failure.c
@@ -64,7 +64,7 @@ int BPF_PROG(cgrp_kfunc_acquire_no_null_check, struct cgroup *cgrp, const char *
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("R1 is fp expected STRUCT cgroup")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(cgrp_kfunc_acquire_fp, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *acquired, *stack_cgrp = (struct cgroup *)&path;
@@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *kptr;
@@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
 {
 	struct cgroup *acquired = (struct cgroup *)&path;
@@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
 }
 
 SEC("tp_btf/cgroup_mkdir")
-__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)
 {
 	/* Cannot release trusted cgroup pointer which was not acquired. */
diff --git a/tools/testing/selftests/bpf/progs/cpumask_failure.c b/tools/testing/selftests/bpf/progs/cpumask_failure.c
index 4628feb53d861..c89c88db39d14 100644
--- a/tools/testing/selftests/bpf/progs/cpumask_failure.c
+++ b/tools/testing/selftests/bpf/progs/cpumask_failure.c
@@ -183,7 +183,7 @@ int BPF_PROG(test_global_mask_no_null_check, struct task_struct *task, u64 clone
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("Possibly NULL pointer passed to helper R2")
+__failure __msg("release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
 int BPF_PROG(test_global_mask_rcu_no_null_check, struct task_struct *task, u64 clone_flags)
 {
 	struct bpf_cpumask *prev, *curr;
@@ -243,7 +243,7 @@ int BPF_PROG(test_populate_invalid_destination, struct task_struct *task, u64 cl
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("leads to invalid memory access")
+__failure __msg("R2 type=scalar expected=fp")
 int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_flags)
 {
 	void *garbage = (void *)0x123456;
diff --git a/tools/testing/selftests/bpf/progs/irq.c b/tools/testing/selftests/bpf/progs/irq.c
index a4a007866a332..53df6d248e267 100644
--- a/tools/testing/selftests/bpf/progs/irq.c
+++ b/tools/testing/selftests/bpf/progs/irq.c
@@ -15,7 +15,7 @@ struct bpf_res_spin_lock lockA __hidden SEC(".data.A");
 struct bpf_res_spin_lock lockB __hidden SEC(".data.B");
 
 SEC("?tc")
-__failure __msg("R1 doesn't point to an irq flag on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int irq_save_bad_arg(struct __sk_buff *ctx)
 {
 	bpf_local_irq_save(&global_flags);
@@ -23,7 +23,7 @@ int irq_save_bad_arg(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 doesn't point to an irq flag on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int irq_restore_bad_arg(struct __sk_buff *ctx)
 {
 	bpf_local_irq_restore(&global_flags);
diff --git a/tools/testing/selftests/bpf/progs/iters.c b/tools/testing/selftests/bpf/progs/iters.c
index c6699159dacdd..65d4c6e01f932 100644
--- a/tools/testing/selftests/bpf/progs/iters.c
+++ b/tools/testing/selftests/bpf/progs/iters.c
@@ -1688,7 +1688,7 @@ int iter_subprog_check_stacksafe(const void *ctx)
 struct bpf_iter_num global_it;
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_new_bad_arg(const void *ctx)
 {
 	bpf_iter_num_new(&global_it, 0, 1);
@@ -1696,7 +1696,7 @@ int iter_new_bad_arg(const void *ctx)
 }
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_next_bad_arg(const void *ctx)
 {
 	bpf_iter_num_next(&global_it);
@@ -1704,7 +1704,7 @@ int iter_next_bad_arg(const void *ctx)
 }
 
 SEC("raw_tp")
-__failure __msg("R1 expected pointer to an iterator on stack")
+__failure __msg("R1 type=map_value expected=fp")
 int iter_destroy_bad_arg(const void *ctx)
 {
 	bpf_iter_num_destroy(&global_it);
diff --git a/tools/testing/selftests/bpf/progs/iters_testmod.c b/tools/testing/selftests/bpf/progs/iters_testmod.c
index 76012dbbdb413..f65cc9766633e 100644
--- a/tools/testing/selftests/bpf/progs/iters_testmod.c
+++ b/tools/testing/selftests/bpf/progs/iters_testmod.c
@@ -105,8 +105,7 @@ int iter_next_rcu_not_trusted(const void *ctx)
 }
 
 SEC("raw_tp/sys_enter")
-__failure __msg("R1 cannot write into rdonly_mem")
-/* Message should not be 'R1 cannot write into rdonly_trusted_mem' */
+__failure __msg("R1 type=rdonly_mem expected=fp")
 int iter_next_ptr_mem_not_trusted(const void *ctx)
 {
 	struct bpf_iter_num num_it;
@@ -135,7 +134,7 @@ int iter_ret_rcu_test_protected(const void *ctx)
 }
 
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
-__failure __msg("R1 type=rcu_ptr_or_null_ expected=")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_ret_rcu_test_type(const void *ctx)
 {
 	struct task_struct *p;
@@ -158,7 +157,7 @@ int iter_ret_rcu_test_protected_nostruct(const void *ctx)
 }
 
 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
-__failure __msg("R1 type=rdonly_rcu_mem_or_null expected=")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int iter_ret_rcu_test_type_nostruct(const void *ctx)
 {
 	void *p;
diff --git a/tools/testing/selftests/bpf/progs/map_kptr_fail.c b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
index eee35d203b66f..ac4003bfb8b09 100644
--- a/tools/testing/selftests/bpf/progs/map_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/map_kptr_fail.c
@@ -149,7 +149,7 @@ int reject_bad_type_match(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 type=untrusted_ptr_or_null_ expected=percpu_ptr_")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int marked_as_untrusted_or_null(struct __sk_buff *ctx)
 {
 	struct map_value *v;
@@ -217,7 +217,7 @@ int reject_kptr_xchg_on_unref(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 type=rcu_ptr_or_null_ expected=percpu_ptr_")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int mark_ref_as_untrusted_or_null(struct __sk_buff *ctx)
 {
 	struct map_value *v;
@@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
+__failure __msg("release function bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
 int reject_untrusted_xchg(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *p;
@@ -291,7 +291,7 @@ int reject_bad_type_xchg(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("invalid kptr access, R2 type=trusted_ptr_prog_test_ref_kfunc")
+__failure __msg("R2 must have zero offset when passed to release func")
 int reject_member_of_ref_xchg(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *ref_ptr;
@@ -364,7 +364,7 @@ int kptr_xchg_ref_state(struct __sk_buff *ctx)
 }
 
 SEC("?tc")
-__failure __msg("Possibly NULL pointer passed to helper R2")
+__success
 int kptr_xchg_possibly_null(struct __sk_buff *ctx)
 {
 	struct prog_test_ref_kfunc *p;
diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
index 3e0d4f687aaad..23019023511af 100644
--- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
+++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c
@@ -118,8 +118,7 @@ int atomic_rmw_not_ok(void *ctx)
 
 SEC("socket")
 __failure
-__msg("invalid access to memory, mem_size=0 off=0 size=4")
-__msg("R1 min value is outside of the allowed memory range")
+__msg("R1 type=rdonly_untrusted_mem expected=fp")
 int kfunc_param_not_ok(void *ctx)
 {
 	int *p;
diff --git a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
index 3701f4ea58c75..57615b0f0c25d 100644
--- a/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
+++ b/tools/testing/selftests/bpf/progs/percpu_alloc_fail.c
@@ -24,6 +24,7 @@ struct val_600b_t {
 struct elem {
 	long sum;
 	struct val_t __percpu_kptr *pc;
+	struct val_t __percpu_kptr *pc2;
 };
 
 struct {
@@ -46,6 +47,8 @@ struct {
 
 struct task_struct *bpf_task_from_pid(s32 pid) __ksym;
 void bpf_task_release(struct task_struct *p) __ksym;
+void bpf_rcu_read_lock(void) __ksym;
+void bpf_rcu_read_unlock(void) __ksym;
 
 long ret;
 
@@ -123,6 +126,38 @@ int BPF_PROG(test_array_map_3)
 	return 0;
 }
 
+SEC("?fentry.s/bpf_fentry_test1")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+int BPF_PROG(reject_nullable_percpu_xchg_alias)
+{
+	struct val_t __percpu_kptr *p1, *p2, *old;
+	struct val_t *v;
+	struct elem *e;
+	int index = 0;
+
+	e = bpf_map_lookup_elem(&array, &index);
+	if (!e)
+		return 0;
+
+	p1 = bpf_percpu_obj_new(struct val_t);
+	p2 = bpf_percpu_obj_new(struct val_t);
+
+	bpf_rcu_read_lock();
+	old = bpf_kptr_xchg(&e->pc, p1);
+	if (old)
+		bpf_percpu_obj_drop(old);
+	old = bpf_kptr_xchg(&e->pc2, p2);
+	if (old)
+		bpf_percpu_obj_drop(old);
+
+	if (p1) {
+		v = bpf_this_cpu_ptr(p2);
+		v->b = 1;
+	}
+	bpf_rcu_read_unlock();
+	return 0;
+}
+
 SEC("?fentry.s/bpf_fentry_test1")
 __failure __msg("R1 expected for bpf_percpu_obj_drop()")
 int BPF_PROG(test_array_map_4)
diff --git a/tools/testing/selftests/bpf/progs/rbtree_fail.c b/tools/testing/selftests/bpf/progs/rbtree_fail.c
index 4504608196abd..08709f23ec0f1 100644
--- a/tools/testing/selftests/bpf/progs/rbtree_fail.c
+++ b/tools/testing/selftests/bpf/progs/rbtree_fail.c
@@ -180,7 +180,7 @@ long rbtree_api_use_unchecked_remove_retval(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
+__failure __msg("R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_api_add_release_unlock_escape(void *ctx)
 {
 	struct node_data *n;
@@ -204,7 +204,7 @@ long rbtree_api_add_release_unlock_escape(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("bpf_rbtree_remove can only take non-owning or refcounted bpf_rb_node pointer")
+__failure __msg("R2 type=scalar expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_api_first_release_unlock_escape(void *ctx)
 {
 	struct bpf_rb_node *res;
diff --git a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
index 338e43822ffec..e80f78fae2276 100644
--- a/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
+++ b/tools/testing/selftests/bpf/progs/refcounted_kptr_fail.c
@@ -118,8 +118,8 @@ long refcount_acquire_maybe_null(void *ctx)
 }
 
 SEC("?tc")
-__failure __msg("R1 is neither owning or non-owning ref")
-__msg("expects a pointer to a BPF-managed refcounted object, but R1 is a context pointer")
+__failure __msg("R1 type=ctx expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
+__msg("type ctx, but this argument accepts ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long refcount_acquire_non_object(void *ctx)
 {
 	return bpf_refcount_acquire(ctx) != NULL;
@@ -159,8 +159,7 @@ long refcount_acquire_rcu_map_kptr_unchecked_drop(void *ctx)
 
 SEC("?syscall")
 __failure
-__msg("bpf_rbtree_remove can only take non-owning or refcounted "
-      "bpf_rb_node pointer")
+__msg("R2 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long rbtree_remove_after_rcu_unlock(void *ctx)
 {
 	struct map_value_rcu_graph *mapval;
@@ -190,7 +189,7 @@ long rbtree_remove_after_rcu_unlock(void *ctx)
 }
 
 SEC("?syscall")
-__failure __msg("R1 is neither owning or non-owning ref")
+__failure __msg("R1 type=untrusted_ptr_ expected=ptr_, rcu_ptr_, ptr_, rcu_ptr_")
 long refcount_acquire_after_rcu_unlock(void *ctx)
 {
 	struct map_value_refcount_only *mapval;
diff --git a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
index 330682a88c161..8fd591bd1f6cf 100644
--- a/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
+++ b/tools/testing/selftests/bpf/progs/res_spin_lock_fail.c
@@ -24,7 +24,7 @@ struct bpf_spin_lock lock __hidden SEC(".data.A");
 struct bpf_res_spin_lock res_lock __hidden SEC(".data.B");
 
 SEC("?tc")
-__failure __msg("point to map value or allocated object")
+__failure __msg("R1 type=untrusted_ptr_ expected=map_value, ptr_")
 int res_spin_lock_arg(struct __sk_buff *ctx)
 {
 	struct arr_elem *elem;
diff --git a/tools/testing/selftests/bpf/progs/stream_fail.c b/tools/testing/selftests/bpf/progs/stream_fail.c
index 21428bb1ee597..10ebb4a7f105a 100644
--- a/tools/testing/selftests/bpf/progs/stream_fail.c
+++ b/tools/testing/selftests/bpf/progs/stream_fail.c
@@ -23,7 +23,7 @@ int stream_vprintk_scalar_arg(void *ctx)
 }
 
 SEC("syscall")
-__failure __msg("R2 doesn't point to a const string")
+__failure __msg("R2 type=ctx expected=map_value")
 int stream_vprintk_string_arg(void *ctx)
 {
 	bpf_stream_vprintk(BPF_STDOUT, ctx, NULL, 0);
diff --git a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
index f96b0c13ed1a5..12c8ac6099cae 100644
--- a/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
+++ b/tools/testing/selftests/bpf/progs/task_kfunc_failure.c
@@ -50,7 +50,7 @@ int BPF_PROG(task_kfunc_acquire_untrusted, struct task_struct *task, u64 clone_f
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("R1 is fp expected STRUCT task_struct")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(task_kfunc_acquire_fp, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *acquired, *stack_task = (struct task_struct *)&clone_flags;
@@ -179,7 +179,7 @@ int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_f
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *acquired = (struct task_struct *)&clone_flags;
@@ -225,7 +225,7 @@ int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_flags)
 {
 	/* Cannot release trusted task pointer which was not acquired. */
@@ -333,7 +333,7 @@ int BPF_PROG(task_access_comm2, struct task_struct *task, u64 clone_flags)
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("write into memory")
+__failure __msg("only read is supported")
 int BPF_PROG(task_access_comm3, struct task_struct *task, u64 clone_flags)
 {
 	bpf_probe_read_kernel(task->comm, 16, task->comm);
@@ -353,7 +353,7 @@ int BPF_PROG(task_access_comm4, struct task_struct *task, const char *buf, bool
 }
 
 SEC("tp_btf/task_newtask")
-__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flags)
 {
 	struct task_struct *local;
diff --git a/tools/testing/selftests/bpf/progs/task_work_fail.c b/tools/testing/selftests/bpf/progs/task_work_fail.c
index 3186e7b4b24e0..bc56bdaca780b 100644
--- a/tools/testing/selftests/bpf/progs/task_work_fail.c
+++ b/tools/testing/selftests/bpf/progs/task_work_fail.c
@@ -58,7 +58,7 @@ int mismatch_map(struct pt_regs *args)
 }
 
 SEC("perf_event")
-__failure __msg("R2 doesn't point to a map value")
+__failure __msg("R2 type=fp expected=map_value")
 int no_map_task_work(struct pt_regs *args)
 {
 	struct task_struct *task;
diff --git a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
index bf48fc43c7ab6..f7a83e5024543 100644
--- a/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
+++ b/tools/testing/selftests/bpf/progs/test_kfunc_dynptr_param.c
@@ -40,7 +40,7 @@ int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size,
 }
 
 SEC("?lsm.s/bpf")
-__failure __msg("R1 expected pointer to stack or const struct bpf_dynptr")
+__failure __msg("R1 type=map_value expected=fp, dynptr_ptr")
 int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
 {
 	static struct bpf_dynptr val;
diff --git a/tools/testing/selftests/bpf/progs/verifier_ctx.c b/tools/testing/selftests/bpf/progs/verifier_ctx.c
index 7856dad3d1f38..9d42ba8244082 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ctx.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ctx.c
@@ -208,7 +208,7 @@ __naked void null_check_7_ctx_bind(void)
 
 SEC("cgroup/post_bind4")
 __description("pass ctx or null check, 8: null (bind)")
-__failure __msg("R1 type=scalar expected=ctx")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void null_check_8_null_bind(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
index a3d2af8dc8396..b277b1efb8c7b 100644
--- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
+++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c
@@ -177,7 +177,7 @@ __weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
 
 SEC("?tp_btf/task_newtask")
 __failure __log_level(2)
-__msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
+__msg("release function bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
 {
 	return subprog_trusted_destroy(task);
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
index 343fc08d97479..d1452ef6f2f9a 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_access_var_len.c
@@ -621,7 +621,7 @@ l0_%=:	exit;						\
 
 SEC("tracepoint")
 __description("helper access to variable memory: size = 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)")
-__failure __msg("R1 type=scalar expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void ptr_to_mem_or_null_8(void)
 {
 	asm volatile ("					\
@@ -637,7 +637,7 @@ __naked void ptr_to_mem_or_null_8(void)
 
 SEC("tracepoint")
 __description("helper access to variable memory: size > 0 not allowed on NULL (!ARG_PTR_TO_MEM_OR_NULL)")
-__failure __msg("R1 type=scalar expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void ptr_to_mem_or_null_9(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
index 71cee3f583243..12786b72c6948 100644
--- a/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
+++ b/tools/testing/selftests/bpf/progs/verifier_helper_packet_access.c
@@ -258,7 +258,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("helper access to packet: test11, cls unsuitable helper 1")
-__failure __msg("helper access to the packet")
+__failure __msg("function access to the packet")
 __naked void test11_cls_unsuitable_helper_1(void)
 {
 	asm volatile ("					\
@@ -283,7 +283,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("helper access to packet: test12, cls unsuitable helper 2")
-__failure __msg("helper access to the packet")
+__failure __msg("function access to the packet")
 __naked void test12_cls_unsuitable_helper_2(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c
new file mode 100644
index 0000000000000..88009566d92f9
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_packet_access.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+struct nf_conn *bpf_skb_ct_lookup(struct __sk_buff *skb_ctx,
+				  struct bpf_sock_tuple *bpf_tuple,
+				  u32 tuple__sz, struct bpf_ct_opts *opts,
+				  u32 opts__sz) __ksym;
+void bpf_ct_release(struct nf_conn *nfct) __ksym;
+
+char _license[] SEC("license") = "GPL";
+
+SEC("tc")
+__description("kfunc packet write requests writable skb")
+__success
+/* bpf_unclone_prologue() */
+__xlated("r6 = *(u8 *)(r1 +{{[0-9]+}})")
+__xlated("...")
+__xlated("w6 &= {{(1|128)}}")
+__xlated("...")
+__xlated("if r6 == 0x0 goto")
+__xlated("r6 = r1")
+__xlated("r2 ^= r2")
+__xlated("call")
+__xlated("if r0 == 0x0 goto")
+__xlated("w0 = 2")
+__xlated("...")
+__xlated("exit")
+__xlated("r1 = r6")
+int kfunc_packet_write(struct __sk_buff *skb)
+{
+	void *data_end = (void *)(long)skb->data_end;
+	void *data = (void *)(long)skb->data;
+	struct bpf_sock_tuple tuple = {};
+	struct nf_conn *nfct;
+
+	if (data + sizeof(struct bpf_ct_opts) > data_end)
+		return 0;
+
+	/* An invalid tuple size makes bpf_skb_ct_lookup() write opts->error. */
+	nfct = bpf_skb_ct_lookup(skb, &tuple, 1, data, sizeof(struct bpf_ct_opts));
+	if (nfct)
+		bpf_ct_release(nfct);
+	return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_live_stack.c b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
index 401152b2b64fc..bc3dfdc1a5363 100644
--- a/tools/testing/selftests/bpf/progs/verifier_live_stack.c
+++ b/tools/testing/selftests/bpf/progs/verifier_live_stack.c
@@ -246,7 +246,7 @@ static __used __naked void read_first_param2(void)
 SEC("socket")
 __flag(BPF_F_TEST_STATE_FREQ)
 __failure
-__msg("R1 type=scalar expected=map_ptr")
+__msg("Possibly NULL pointer passed to trusted R1")
 __naked void caller_stack_pruning_callback(void)
 {
 	asm volatile (
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 d3be69a9a7557..621248a02a1f9 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_in_map.c
@@ -154,8 +154,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 __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void on_the_inner_map_pointer(void)
 {
diff --git a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
index c01abf54923d3..4b1eadddd89cd 100644
--- a/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
+++ b/tools/testing/selftests/bpf/progs/verifier_map_lookup_refine.c
@@ -58,7 +58,7 @@ int mapofmaps_value_as_helper_mem_buf(struct __sk_buff *skb)
 }
 
 SEC("?tc")
-__failure __msg("type=map_ptr_or_null expected=fp")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 int mapofmaps_value_as_helper_fixed_mem(struct __sk_buff *skb)
 {
 	char th[sizeof(struct tcphdr)] = {};
diff --git a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
index 199ad18f8eb58..799db6f5713b0 100644
--- a/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
+++ b/tools/testing/selftests/bpf/progs/verifier_ref_tracking.c
@@ -344,7 +344,7 @@ __naked void potential_reference_to_system_key(void)
 
 SEC("tc")
 __description("reference tracking: release reference without check")
-__failure __msg("type=sock_or_null expected=sock")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void tracking_release_reference_without_check(void)
 {
 	asm volatile (
@@ -363,7 +363,7 @@ __naked void tracking_release_reference_without_check(void)
 
 SEC("tc")
 __description("reference tracking: release reference to sock_common without check")
-__failure __msg("type=sock_common_or_null expected=sock")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __naked void to_sock_common_without_check(void)
 {
 	asm volatile (
@@ -1288,7 +1288,7 @@ l1_%=:	r1 = r6;					\
 
 SEC("tc")
 __description("reference tracking: bpf_sk_release(listen_sk)")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_release_listen_sk(void)
 {
 	asm volatile (
diff --git a/tools/testing/selftests/bpf/progs/verifier_sock.c b/tools/testing/selftests/bpf/progs/verifier_sock.c
index 4f2f3209eec81..2a136c917680f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_sock.c
+++ b/tools/testing/selftests/bpf/progs/verifier_sock.c
@@ -110,7 +110,7 @@ l0_%=:	r0 = *(u32*)(r1 + %[bpf_sock_type]);		\
 
 SEC("cgroup/skb")
 __description("bpf_sk_fullsock(skb->sk): no !skb->sk check")
-__failure __msg("type=sock_common_or_null expected=sock_common")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void sk_no_skb_sk_check_1(void)
 {
@@ -466,7 +466,7 @@ l1_%=:	r0 = *(u32*)(r0 + %[bpf_sock_rx_queue_mapping__end]);\
 
 SEC("cgroup/skb")
 __description("bpf_tcp_sock(skb->sk): no !skb->sk check")
-__failure __msg("type=sock_common_or_null expected=sock_common")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
 __failure_unpriv
 __naked void sk_no_skb_sk_check_2(void)
 {
@@ -603,7 +603,7 @@ l2_%=:	r0 = *(u32*)(r0 + %[bpf_tcp_sock_snd_cwnd]);	\
 
 SEC("tc")
 __description("bpf_sk_release(skb->sk)")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_release_skb_sk(void)
 {
 	asm volatile ("					\
@@ -620,7 +620,7 @@ l0_%=:	r0 = 0;						\
 
 SEC("tc")
 __description("bpf_sk_release(bpf_sk_fullsock(skb->sk))")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_sk_fullsock_skb_sk(void)
 {
 	asm volatile ("					\
@@ -644,7 +644,7 @@ l1_%=:	r1 = r0;					\
 
 SEC("tc")
 __description("bpf_sk_release(bpf_tcp_sock(skb->sk))")
-__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
 __naked void bpf_tcp_sock_skb_sk(void)
 {
 	asm volatile ("					\
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index 8f0c45421f893..b5f456d57669e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -28,7 +28,7 @@ int BPF_PROG(get_task_exe_file_kfunc_null)
 }
 
 SEC("lsm.s/inode_getxattr")
-__failure __msg("R1 is fp expected STRUCT task_struct")
+__failure __msg("R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_")
 int BPF_PROG(get_task_exe_file_kfunc_fp)
 {
 	u64 x;
@@ -80,7 +80,7 @@ int BPF_PROG(get_task_exe_file_kfunc_unreleased)
 }
 
 SEC("lsm.s/file_open")
-__failure __msg("release kfunc bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("release function bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
 int BPF_PROG(put_file_kfunc_unacquired, struct file *file)
 {
 	/* Can't release an unacquired pointer. */
@@ -128,7 +128,7 @@ int BPF_PROG(path_d_path_kfunc_untrusted_from_current)
 }
 
 SEC("lsm.s/file_open")
-__failure __msg("kernel function bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
+__failure __msg("bpf_path_d_path R1 expected pointer to STRUCT path but R1 has a pointer to STRUCT file")
 int BPF_PROG(path_d_path_kfunc_type_mismatch, struct file *file)
 {
 	bpf_path_d_path((struct path *)&file->f_task_work, buf, sizeof(buf));
diff --git a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
index d4d0f1610853a..ec4e0f3ff7920 100644
--- a/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
+++ b/tools/testing/selftests/bpf/progs/wakeup_source_fail.c
@@ -42,7 +42,7 @@ int wakeup_source_access_lock_fields(void *ctx)
 }
 
 SEC("syscall")
-__failure __msg("release kfunc bpf_wakeup_sources_read_unlock expects referenced PTR_TO_BTF_ID passed to R1")
+__failure __msg("R1 type=scalar expected=ptr_, trusted_ptr_, rcu_ptr_")
 int wakeup_source_unlock_no_lock(void *ctx)
 {
 	struct bpf_ws_lock *lock = (void *)0x1;
diff --git a/tools/testing/selftests/bpf/progs/wq_failures.c b/tools/testing/selftests/bpf/progs/wq_failures.c
index 32dc8827e128b..bd30217579d4d 100644
--- a/tools/testing/selftests/bpf/progs/wq_failures.c
+++ b/tools/testing/selftests/bpf/progs/wq_failures.c
@@ -48,7 +48,7 @@ __log_level(2)
 __flag(BPF_F_TEST_STATE_FREQ)
 __failure
 __msg(": (85) call bpf_wq_init#") /* anchor message */
-__msg("pointer in R2 isn't map pointer")
+__msg("R2 type=fp expected=map_ptr")
 long test_wq_init_nomap(void *ctx)
 {
 	struct bpf_wq *wq;
@@ -98,7 +98,7 @@ __failure
  * is a correct bpf_wq pointer.
  */
 __msg(": (85) call bpf_wq_set_callback#") /* anchor message */
-__msg("R1 doesn't point to a map value")
+__msg("R1 type=fp expected=map_value")
 long test_wrong_wq_pointer(void *ctx)
 {
 	int key = 0;
diff --git a/tools/testing/selftests/bpf/verifier/calls.c b/tools/testing/selftests/bpf/verifier/calls.c
index eb6e3baef412a..8b94b87135bcf 100644
--- a/tools/testing/selftests/bpf/verifier/calls.c
+++ b/tools/testing/selftests/bpf/verifier/calls.c
@@ -31,7 +31,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 is fp expected STRUCT prog_test_fail1",
+	.errstr = "R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail1", 2 },
 	},
@@ -46,7 +46,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "max struct nesting depth exceeded\nR1 is fp expected STRUCT prog_test_fail2",
+	.errstr = "max struct nesting depth exceeded\nR1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail2", 2 },
 	},
@@ -61,7 +61,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 is fp expected STRUCT prog_test_fail3",
+	.errstr = "R1 type=fp expected=ptr_, trusted_ptr_, rcu_ptr_",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_fail3", 2 },
 	},
@@ -76,7 +76,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "R1 expected pointer to ctx, but got fp",
+	.errstr = "R1 type=fp expected=ctx",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_test_pass_ctx", 2 },
 	},
@@ -152,7 +152,7 @@
 	},
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.result = REJECT,
-	.errstr = "kernel function bpf_kfunc_call_memb1_release R1 expected pointer",
+	.errstr = "bpf_kfunc_call_memb1_release R1 expected pointer",
 	.fixup_kfunc_btf_id = {
 		{ "bpf_kfunc_call_memb_acquire", 1 },
 		{ "bpf_kfunc_call_memb1_release", 5 },
diff --git a/tools/testing/selftests/bpf/verifier/map_kptr.c b/tools/testing/selftests/bpf/verifier/map_kptr.c
index 4b39f8472f9b5..345cecc722a38 100644
--- a/tools/testing/selftests/bpf/verifier/map_kptr.c
+++ b/tools/testing/selftests/bpf/verifier/map_kptr.c
@@ -311,7 +311,7 @@
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.fixup_map_kptr = { 1 },
 	.result = REJECT,
-	.errstr = "R1 type=rcu_ptr_or_null_ expected=percpu_ptr_",
+	.errstr = "Possibly NULL pointer passed to trusted R1",
 },
 {
 	"map_kptr: ref: reject off != 0",
@@ -342,7 +342,7 @@
 	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
 	.fixup_map_kptr = { 1 },
 	.result = REJECT,
-	.errstr = "invalid kptr access, R2 type=ptr_prog_test_ref_kfunc expected=ptr_prog_test_member",
+	.errstr = "R2 must have zero offset when passed to release func",
 },
 {
 	"map_kptr: ref: reference state created and released on xchg",

25/3 2026/09/11 22:36 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64829
							output: 23
							thoughts: 2025
						

Thoughts:
**KMSAN Fuzzing Session Assessment for BPF Verifier Patch**

Okay, let's break this down. The user wants me to determine if we need a dedicated KMSAN fuzzing session for this BPF verifier patch. Right off the bat, I need to remind myself that KMSAN is for catching uninitialized memory reads and kernel-to-user info leaks, things that KASAN and other standard checks don't necessarily catch. The rules are clear: if standard bug detectors (KASAN, LOCKDEP, etc.) cover it, KMSAN is a waste of resources. I've got to focus on whether this patch introduces new risks specifically related to uninitialized memory.

First, I need to understand what this patch actually *does*. It looks like a significant refactor of the BPF verifier, unifying the argument checking logic for helpers, kfuncs, and subprograms. A lot of the work is centered on standardizing and extending argument types within the verifier, renaming a few, and changing how kfunc arguments are handled. It's clearly a cleanup and unification effort.

Now, I have to think like KMSAN. This patch introduces several new `enum bpf_arg_type` definitions, including various pointer types (`ARG_PTR_TO_ALLOC_BTF_ID`, `ARG_PTR_TO_REFCOUNTED_KPTR`, etc.) and scalar/memory size arguments. This looks like the main area I should concentrate on. I need to figure out where those enums are used and how they relate to memory initialization. Specifically, I need to consider whether these new types could create new chances for uninitialized memory to be read.

I've already noticed mentions of `MEM_UNINIT`, which is a good sign. The code seems to explicitly track whether a dynptr argument is considered uninitialized. The patch also includes a function `is_kfunc_arg_uninit`, which now maps the argument type to the flag. This seems like a potential change to how the verifier tracks or handles uninitialized memory, which is critical. The logic used to detect uninitialized arguments, previously handled in `check_kfunc_args`, has been moved to a unified argument checking system. This is where the risk lies. The old logic might have been less thorough or more narrowly applied than this new consolidated approach.

My initial review suggests that the patch *might* introduce new opportunities for KMSAN to find bugs. The refactoring itself seems to be the risky part. If the unification accidentally misses a case where uninitialized memory could be read, then KMSAN might be needed. So, to decide, I need to dig deeper:

1.  **Fully understand how `is_kfunc_arg_uninit` and `MEM_UNINIT` are now used in the unified `check_func_arg` function**. I'll need to trace the data flow through all the new argument types and ensure the checks are sound.
2.  **Carefully examine the cases where the new `bpf_arg_type` enums are used**. Are these enums correctly initialized? Are they used in contexts where an uninitialized value could be read? The goal is to determine if the changes potentially expose previously unchecked, uninitialized memory reads.
3.  **Check the tests:** See if any new KMSAN tests were added with the patch. If they weren't, consider adding tests for these newly supported types.

In short: while the patch is a significant refactor, the addition and movement of code related to argument type checking and uninitialized memory tracking raises a flag for further investigation. There is a *risk* that this patch could introduce uninitialized memory reads that standard sanitizers miss, so I need to go deeper to determine if a KMSAN session is truly needed.



26/3 2026/09/11 22:37 tool grepper
0m Args:
{
  "Expression": "is_kfunc_arg_uninit"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=12031=static bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)\n--\nkernel/bpf/verifier.c-12035-\nkernel/bpf/verifier.c:12036:static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)\nkernel/bpf/verifier.c-12037-{\n--\nkernel/bpf/verifier.c=12661=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12823-\nkernel/bpf/verifier.c:12824:\tif (is_kfunc_arg_uninit(meta-\u003ebtf, \u0026args[arg]))\nkernel/bpf/verifier.c-12825-\t\targ_type |= MEM_UNINIT;\n--\nkernel/bpf/verifier.c=13614=s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-13668-\t\treturn -size;\nkernel/bpf/verifier.c:13669:\tif (is_kfunc_arg_uninit(btf, \u0026args[arg]))\nkernel/bpf/verifier.c-13670-\t\treturn -size;\n"
}

27/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 68074
							output: 18
							thoughts: 55
						

Thoughts:
**Digging into Uninitialized Memory**

Okay, so I'm looking at this `is_kfunc_arg_uninit` function within `get_kfunc_arg_type`. Seems like a straightforward check, but hang on... what's the deal with `MEM_UNINIT`? I know I've seen it before, but a quick refresher never hurts, especially when dealing with potentially undefined behavior. Better to be safe than sorry. Let's trace where `MEM_UNINIT` is actually *used* within this code base. That should give me a better understanding of its implications in this context and clarify its role in determining argument types. Time to get the search function fired up.



28/3 2026/09/11 22:37 tool grepper
0m Args:
{
  "Expression": "MEM_UNINIT"
}

Results:
{
  "Output": "include/linux/bpf.h=749=enum bpf_type_flag {\n--\ninclude/linux/bpf.h-783-\t/* MEM can be uninitialized. */\ninclude/linux/bpf.h:784:\tMEM_UNINIT\t\t= BIT(7 + BPF_BASE_TYPE_BITS),\ninclude/linux/bpf.h-785-\n--\ninclude/linux/bpf.h-848-\ninclude/linux/bpf.h:849:\t/* MEM is being written to, often combined with MEM_UNINIT. Non-presence\ninclude/linux/bpf.h-850-\t * of MEM_WRITE means that MEM is only being read. MEM_WRITE without the\ninclude/linux/bpf.h:851:\t * MEM_UNINIT means that memory needs to be initialized since it is also\ninclude/linux/bpf.h-852-\t * read.\n--\ninclude/linux/bpf.h=876=enum bpf_arg_type {\n--\ninclude/linux/bpf.h-939-\t */\ninclude/linux/bpf.h:940:\tARG_PTR_TO_UNINIT_MEM\t\t= MEM_UNINIT | MEM_WRITE | ARG_PTR_TO_MEM,\ninclude/linux/bpf.h-941-\t/* Pointer to valid memory of size known at compile time. */\n--\nkernel/bpf/helpers.c=112=const struct bpf_func_proto bpf_map_pop_elem_proto = {\n--\nkernel/bpf/helpers.c-116-\t.arg1_type\t= ARG_CONST_MAP_PTR,\nkernel/bpf/helpers.c:117:\t.arg2_type\t= ARG_PTR_TO_MAP_VALUE | MEM_UNINIT | MEM_WRITE,\nkernel/bpf/helpers.c-118-};\n--\nkernel/bpf/helpers.c=125=const struct bpf_func_proto bpf_map_peek_elem_proto = {\n--\nkernel/bpf/helpers.c-129-\t.arg1_type\t= ARG_CONST_MAP_PTR,\nkernel/bpf/helpers.c:130:\t.arg2_type\t= ARG_PTR_TO_MAP_VALUE | MEM_UNINIT | MEM_WRITE,\nkernel/bpf/helpers.c-131-};\n--\nkernel/bpf/helpers.c=537=const struct bpf_func_proto bpf_strtol_proto = {\n--\nkernel/bpf/helpers.c-543-\t.arg3_type\t= ARG_ANYTHING,\nkernel/bpf/helpers.c:544:\t.arg4_type\t= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,\nkernel/bpf/helpers.c-545-\t.arg4_size\t= sizeof(s64),\n--\nkernel/bpf/helpers.c=565=const struct bpf_func_proto bpf_strtoul_proto = {\n--\nkernel/bpf/helpers.c-571-\t.arg3_type\t= ARG_ANYTHING,\nkernel/bpf/helpers.c:572:\t.arg4_type\t= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,\nkernel/bpf/helpers.c-573-\t.arg4_size\t= sizeof(u64),\n--\nkernel/bpf/helpers.c=1887=static const struct bpf_func_proto bpf_dynptr_from_mem_proto = {\n--\nkernel/bpf/helpers.c-1893-\t.arg3_type\t= ARG_ANYTHING,\nkernel/bpf/helpers.c:1894:\t.arg4_type\t= ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_LOCAL | MEM_UNINIT | MEM_WRITE,\nkernel/bpf/helpers.c-1895-};\n--\nkernel/bpf/ringbuf.c=701=const struct bpf_func_proto bpf_ringbuf_reserve_dynptr_proto = {\n--\nkernel/bpf/ringbuf.c-706-\t.arg3_type\t= ARG_ANYTHING,\nkernel/bpf/ringbuf.c:707:\t.arg4_type\t= ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF | MEM_UNINIT | MEM_WRITE,\nkernel/bpf/ringbuf.c-708-};\n--\nkernel/bpf/syscall.c=6618=static const struct bpf_func_proto bpf_kallsyms_lookup_name_proto = {\n--\nkernel/bpf/syscall.c-6624-\t.arg3_type\t= ARG_ANYTHING,\nkernel/bpf/syscall.c:6625:\t.arg4_type\t= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,\nkernel/bpf/syscall.c-6626-\t.arg4_size\t= sizeof(u64),\n--\nkernel/bpf/verifier.c=7656=__printf(6, 7) static void bpf_diag_call_arg_fmt(struct bpf_verifier_env *env, u32 insn_idx,\n--\nkernel/bpf/verifier.c-7663- * @meta carries the dynptr and referenced-object state. The dynptr is populated\nkernel/bpf/verifier.c:7664: * when the argument is tagged with MEM_UNINIT (i.e., the dynptr argument that\nkernel/bpf/verifier.c-7665- * will be constructed) and consumed when the argument is expected to be an\n--\nkernel/bpf/verifier.c=7683=static int process_dynptr_func(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7700-\nkernel/bpf/verifier.c:7701:\t/*  MEM_UNINIT - Points to memory that is an appropriate candidate for\nkernel/bpf/verifier.c-7702-\t *\t\t constructing a mutable bpf_dynptr object.\n--\nkernel/bpf/verifier.c-7714-\t */\nkernel/bpf/verifier.c:7715:\tif (arg_type \u0026 MEM_UNINIT) {\nkernel/bpf/verifier.c-7716-\t\tint i;\n--\nkernel/bpf/verifier.c=8154=static bool arg_type_is_raw_mem(enum bpf_arg_type type)\n--\nkernel/bpf/verifier.c-8162-\t\tbase_type(type) == ARG_PTR_TO_MAP_VALUE) \u0026\u0026\nkernel/bpf/verifier.c:8163:\t       type \u0026 MEM_UNINIT;\nkernel/bpf/verifier.c-8164-}\n--\nkernel/bpf/verifier.c=8888=static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n--\nkernel/bpf/verifier.c-9274-\t\tif (is_bpf_dynptr_clone_kfunc(meta) \u0026\u0026\nkernel/bpf/verifier.c:9275:\t\t    (arg_type \u0026 MEM_UNINIT)) {\nkernel/bpf/verifier.c-9276-\t\t\tenum bpf_dynptr_type parent_type = meta-\u003edynptr.type;\n--\nkernel/bpf/verifier.c=12661=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12824-\tif (is_kfunc_arg_uninit(meta-\u003ebtf, \u0026args[arg]))\nkernel/bpf/verifier.c:12825:\t\targ_type |= MEM_UNINIT;\nkernel/bpf/verifier.c-12826-\n--\nkernel/bpf/verifier.c=13496=s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-13575-\t\t\t */\nkernel/bpf/verifier.c:13576:\t\t\tif (at \u0026 MEM_UNINIT)\nkernel/bpf/verifier.c-13577-\t\t\t\treturn 0;\n--\nkernel/bpf/verifier.c-13595-\t/*\nkernel/bpf/verifier.c:13596:\t * MEM_UNINIT args are write-only: the helper initializes the\nkernel/bpf/verifier.c-13597-\t * buffer without reading it.\nkernel/bpf/verifier.c-13598-\t */\nkernel/bpf/verifier.c:13599:\tif (at \u0026 MEM_UNINIT)\nkernel/bpf/verifier.c-13600-\t\treturn -size;\n--\nkernel/trace/bpf_trace.c=1235=static const struct bpf_func_proto bpf_get_func_arg_proto = {\n--\nkernel/trace/bpf_trace.c-1239-\t.arg2_type\t= ARG_ANYTHING,\nkernel/trace/bpf_trace.c:1240:\t.arg3_type\t= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,\nkernel/trace/bpf_trace.c-1241-\t.arg3_size\t= sizeof(u64),\n--\nkernel/trace/bpf_trace.c=1253=static const struct bpf_func_proto bpf_get_func_ret_proto = {\n--\nkernel/trace/bpf_trace.c-1256-\t.arg1_type\t= ARG_PTR_TO_CTX,\nkernel/trace/bpf_trace.c:1257:\t.arg2_type\t= ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,\nkernel/trace/bpf_trace.c-1258-\t.arg2_size\t= sizeof(u64),\n"
}

29/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 70897
							output: 38
							thoughts: 149
						

Thoughts:
**Analyzing the `is_kfunc_arg_uninit` Function**

Okay, let's dive into this. I'm looking at the `is_kfunc_arg_uninit` function within `kernel/bpf/verifier.c`. Specifically:

```c
static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)
{
	return btf_param_match_suffix(btf, arg, "__uninit");
}
```

It seems pretty straightforward – a boolean check using `btf_param_match_suffix` to see if a function argument has the "__uninit" suffix. Now, the real question is, *where* was this function being used in the older versions of the codebase? What checks relied on identifying uninitialized arguments?

I need to see the context. I'll check the git log, or if that's not easily accessible, maybe I can just grep the old code for it. Otherwise, I will read lines 12030 to 12045 of `kernel/bpf/verifier.c` to see if it is used there.



30/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 12030,
  "LineCount": 25
}

Results:
{
  "Contents": "12030:\t\n12031:\tstatic bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)\n12032:\t{\n12033:\t\treturn btf_param_match_suffix(btf, arg, \"__alloc\");\n12034:\t}\n12035:\t\n12036:\tstatic bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)\n12037:\t{\n12038:\t\treturn btf_param_match_suffix(btf, arg, \"__uninit\");\n12039:\t}\n12040:\t\n12041:\tstatic bool is_kfunc_arg_refcounted_kptr(const struct btf *btf, const struct btf_param *arg)\n12042:\t{\n12043:\t\treturn btf_param_match_suffix(btf, arg, \"__refcounted_kptr\");\n12044:\t}\n12045:\t\n12046:\tstatic bool is_kfunc_arg_nullable(const struct btf *btf, const struct btf_param *arg)\n12047:\t{\n12048:\t\treturn btf_param_match_suffix(btf, arg, \"__nullable\") ||\n12049:\t\t       btf_param_match_suffix(btf, arg, \"__arena\");\n12050:\t}\n12051:\t\n12052:\tstatic bool is_kfunc_arg_nonown_allowed(const struct btf *btf, const struct btf_param *arg)\n12053:\t{\n12054:\t\treturn btf_param_match_suffix(btf, arg, \"__nonown_allowed\");\n"
}

31/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 71831
							output: 38
							thoughts: 983
						

Thoughts:
**Analyzing the Unified Argument Checking Patch**

Okay, let's break this down. My initial concern was around how the handling of `is_kfunc_arg_uninit` had changed, specifically in the context of `dynptr` arguments. I noticed that the old code had a specific check for `KF_ARG_PTR_TO_DYNPTR` within `check_kfunc_args` involving `is_kfunc_arg_uninit`, and the new code seems to fold this check into `get_kfunc_arg_type`. I need to ensure this change doesn't introduce any regressions or overlook uninitialized memory scenarios, especially for arguments that *aren't* dynptrs but are still flagged as `__uninit`. I see that the `bpf_kfunc_stack_access_bytes` and `bpf_helper_stack_access_bytes` already handle `MEM_UNINIT`. That's good; it means the uninit check is still in place.

So, the bigger picture here is argument checking unification. Previously, helpers and kfuncs had their own separate checking mechanisms: `check_helper_call` calling `check_func_arg` for helpers and `check_kfunc_call` calling `check_kfunc_args` for kfuncs. This patch clearly aims to unify those approaches, which should reduce code duplication and hopefully improve maintainability. The refactoring includes expanding the `bpf_arg_type` enum to encompass types that were previously kfunc-specific, and replacing the separate `check_kfunc_args` with a unified `check_func_args` which in turn calls `check_func_arg`. The helper call also now calls `check_func_args`. The proto generation for kfuncs is also unified in `gen_kfunc_arg_proto`.

My focus now is on the implementation details and potential edge cases introduced by this unification. I'm already looking for potential issues: Are there any uninitialized variables, info leaks, or uninitialized memory vulnerabilities being introduced? Let's check some key parts of the code.

1.  In `btf_check_func_arg_match`, `bpf_call_arg_meta meta;` is being `memset` to 0. That looks safe.
2.  In `check_helper_call`, the `meta` struct is also being `memset` to 0. Good.
3.  Let's look at `check_kfunc_call` and see what's going on... I need to see how the call to `check_func_args` is set up.

Okay, let's proceed methodically and check for any oversights in the new approach.



32/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 13930,
  "LineCount": 40
}

Results:
{
  "Contents": "13930:\t\t}\n13931:\t\n13932:\t\tsleepable = bpf_is_kfunc_sleepable(\u0026meta);\n13933:\t\tif (sleepable \u0026\u0026 !in_sleepable(env)) {\n13934:\t\t\tverbose(env, \"program must be sleepable to call sleepable kfunc %s\\n\", func_name);\n13935:\t\t\toperation = bpf_diag_fmt(env, \"sleepable kfunc %s\", func_name);\n13936:\t\t\tbpf_diag_ctx_forbidden(env, insn_idx, operation,\n13937:\t\t\t\t\"Mark the program sleepable if the program type allows it, or use a non-sleepable kfunc.\");\n13938:\t\t\treturn -EACCES;\n13939:\t\t}\n13940:\t\n13941:\t\t/* Track non-sleepable context for kfuncs, same as for helpers. */\n13942:\t\tif (!in_sleepable_context(env))\n13943:\t\t\tinsn_aux-\u003enon_sleepable = true;\n13944:\t\n13945:\t\t/* Check the arguments */\n13946:\t\terr = check_func_args(env, \u0026meta, insn_idx);\n13947:\t\tif (err \u003c 0)\n13948:\t\t\treturn err;\n13949:\t\n13950:\t\tif ((is_bpf_obj_drop_kfunc(meta.func_id) ||\n13951:\t\t     is_bpf_percpu_obj_drop_kfunc(meta.func_id)) \u0026\u0026 (is_tracing_prog_type(prog_type) ||\n13952:\t\t     /* is_tracing_prog_type() for now doesn't cover non-iterator tracing progs. */\n13953:\t\t     (prog_type == BPF_PROG_TYPE_TRACING \u0026\u0026 env-\u003eprog-\u003eexpected_attach_type != BPF_TRACE_ITER\n13954:\t\t      \u0026\u0026 !env-\u003eprog-\u003esleepable))) {\n13955:\t\t\tstruct btf_struct_meta *struct_meta;\n13956:\t\n13957:\t\t\tstruct_meta = btf_find_struct_meta(meta.arg_btf, meta.arg_btf_id);\n13958:\t\t\tif (struct_meta \u0026\u0026 btf_record_has_nmi_unsafe_fields(struct_meta-\u003erecord)) {\n13959:\t\t\t\tverbose(env, \"%s cannot be used in tracing programs on types with NMI unsafe fields\\n\",\n13960:\t\t\t\t\tfunc_name);\n13961:\t\t\t\treturn -EINVAL;\n13962:\t\t\t}\n13963:\t\t}\n13964:\t\n13965:\t\tif (is_bpf_rbtree_add_kfunc(meta.func_id)) {\n13966:\t\t\terr = push_callback_call(env, insn, insn_idx, meta.subprogno,\n13967:\t\t\t\t\t\t set_rbtree_add_callback_state);\n13968:\t\t\tif (err) {\n13969:\t\t\t\tverbose(env, \"kfunc %s#%d failed callback verification\\n\",\n"
}

33/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 74272
							output: 38
							thoughts: 33
						

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

**Analyzing the KFunc Call Initialization**

Alright, let's dive back into `check_kfunc_call` to understand how the `meta` variable is getting initialized. I need to trace its origins within this function, really understand the context of its first use. I'll need to scroll back and see what's happening *before* the point where I'm currently looking. Context is key here; it's probably getting set up early on, likely passed in, or maybe calculated based on other arguments. Understanding the assignment to `meta` is going to be crucial to understanding this.



34/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 13885,
  "LineCount": 45
}

Results:
{
  "Contents": "13885:\t\tdesc = find_kfunc_desc(env-\u003eprog, insn-\u003eimm, insn-\u003eoff);\n13886:\t\tif (!desc) {\n13887:\t\t\tverifier_bug(env, \"kfunc descriptor not found for func_id %u\", insn-\u003eimm);\n13888:\t\t\treturn -EFAULT;\n13889:\t\t}\n13890:\t\tmeta.fn = \u0026desc-\u003eproto;\n13891:\t\n13892:\t\tinsn_aux-\u003eis_iter_next = bpf_is_iter_next_kfunc(\u0026meta);\n13893:\t\n13894:\t\tif (!insn-\u003eoff \u0026\u0026\n13895:\t\t    (insn-\u003eimm == special_kfunc_list[KF_bpf_res_spin_lock] ||\n13896:\t\t     insn-\u003eimm == special_kfunc_list[KF_bpf_res_spin_lock_irqsave])) {\n13897:\t\t\tstruct bpf_verifier_state *branch;\n13898:\t\t\tstruct bpf_reg_state *regs;\n13899:\t\n13900:\t\t\tbranch = push_stack(env, env-\u003einsn_idx + 1, env-\u003einsn_idx, false);\n13901:\t\t\tif (IS_ERR(branch)) {\n13902:\t\t\t\tverbose(env, \"failed to push state for failed lock acquisition\\n\");\n13903:\t\t\t\treturn PTR_ERR(branch);\n13904:\t\t\t}\n13905:\t\n13906:\t\t\tregs = branch-\u003eframe[branch-\u003ecurframe]-\u003eregs;\n13907:\t\n13908:\t\t\t/* Clear r0-r5 registers in forked state */\n13909:\t\t\tfor (i = 0; i \u003c CALLER_SAVED_REGS; i++)\n13910:\t\t\t\tbpf_mark_reg_not_init(env, \u0026regs[caller_saved[i]]);\n13911:\t\n13912:\t\t\tmark_reg_unknown(env, regs, BPF_REG_0);\n13913:\t\t\terr = __mark_reg_s32_range(env, regs, BPF_REG_0, -MAX_ERRNO, -1);\n13914:\t\t\tif (err) {\n13915:\t\t\t\tverbose(env, \"failed to mark s32 range for retval in forked state for lock\\n\");\n13916:\t\t\t\treturn err;\n13917:\t\t\t}\n13918:\t\t} else if (!insn-\u003eoff \u0026\u0026 insn-\u003eimm == special_kfunc_list[KF___bpf_trap]) {\n13919:\t\t\tverbose(env, \"unexpected __bpf_trap() due to uninitialized variable?\\n\");\n13920:\t\t\treturn -EFAULT;\n13921:\t\t}\n13922:\t\n13923:\t\tif (is_kfunc_destructive(\u0026meta) \u0026\u0026 !capable(CAP_SYS_BOOT)) {\n13924:\t\t\tverbose(env, \"destructive kfunc calls require CAP_SYS_BOOT capability\\n\");\n13925:\t\t\toperation = bpf_diag_fmt(env, \"destructive kfunc %s\", meta.func_name);\n13926:\t\t\tbpf_diag_policy(\n13927:\t\t\t\tenv, insn_idx, operation, \"destructive kfuncs require CAP_SYS_BOOT\",\n13928:\t\t\t\t\"Load the program with CAP_SYS_BOOT, or avoid destructive kfuncs.\");\n13929:\t\t\treturn -EACCES;\n"
}

35/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 75411
							output: 38
							thoughts: 51
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into `bpf_fetch_kfunc_arg_meta` Initialization**

Okay, so I'm looking at how `bpf_fetch_kfunc_arg_meta` initializes this `meta` structure. I need to understand this deeply – it’s a critical piece for how the verifier handles kernel function argument metadata. Right, let's zero in on those lines in `kernel/bpf/verifier.c`. Specifically, I'm focusing on lines 13850 to 13885. This range should give me the core initialization logic. I need to see what's getting populated, how it's being done, and what potential issues might be lurking in those operations. Understanding this granular level is key to spotting subtle bugs or performance bottlenecks in the bigger picture. Time to dig in.



36/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 13850,
  "LineCount": 40
}

Results:
{
  "Contents": "13850:\tstatic int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n13851:\t\t\t\t    int *insn_idx_p)\n13852:\t{\n13853:\t\tbool sleepable, rcu_lock, rcu_unlock, preempt_disable, preempt_enable;\n13854:\t\tenum bpf_prog_type prog_type = resolve_prog_type(env-\u003eprog);\n13855:\t\tstruct bpf_reg_state *regs = cur_regs(env);\n13856:\t\tconst char *func_name, *ptr_type_name;\n13857:\t\tconst struct btf_type *t, *ptr_type;\n13858:\t\tstruct bpf_call_arg_meta meta;\n13859:\t\tstruct bpf_insn_aux_data *insn_aux;\n13860:\t\tconst char *operation;\n13861:\t\tint err, insn_idx = *insn_idx_p;\n13862:\t\tu32 i, nargs, ptr_type_id, ret_nregs = 1;\n13863:\t\tstruct bpf_kfunc_desc *desc;\n13864:\t\tstruct btf *desc_btf;\n13865:\t\tint id;\n13866:\t\n13867:\t\t/* skip for now, but return error when we find this in fixup_kfunc_call */\n13868:\t\tif (!insn-\u003eimm)\n13869:\t\t\treturn 0;\n13870:\t\n13871:\t\terr = bpf_fetch_kfunc_arg_meta(env, insn-\u003eimm, insn-\u003eoff, \u0026meta);\n13872:\t\tif (err == -EACCES \u0026\u0026 meta.func_name) {\n13873:\t\t\tverbose(env, \"calling kernel function %s is not allowed\\n\", meta.func_name);\n13874:\t\t\toperation = bpf_diag_fmt(env, \"kfunc %s\", meta.func_name);\n13875:\t\t\tbpf_diag_policy(\n13876:\t\t\t\tenv, insn_idx, operation, \"this program cannot call the kfunc\",\n13877:\t\t\t\t\"Use a kfunc allowed for this program type and attach point, or change the program context.\");\n13878:\t\t}\n13879:\t\tif (err)\n13880:\t\t\treturn err;\n13881:\t\tdesc_btf = meta.btf;\n13882:\t\tfunc_name = meta.func_name;\n13883:\t\tinsn_aux = \u0026env-\u003einsn_aux_data[insn_idx];\n13884:\t\n13885:\t\tdesc = find_kfunc_desc(env-\u003eprog, insn-\u003eimm, insn-\u003eoff);\n13886:\t\tif (!desc) {\n13887:\t\t\tverifier_bug(env, \"kfunc descriptor not found for func_id %u\", insn-\u003eimm);\n13888:\t\t\treturn -EFAULT;\n13889:\t\t}\n"
}

37/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 76520
							output: 38
							thoughts: 45
						

Thoughts:
Here's my take on those thoughts:

**Analyzing `bpf_fetch_kfunc_arg_meta`**

Okay, so I'm digging into `bpf_fetch_kfunc_arg_meta` again. Seems like a key piece I need to understand, and I need to refresh my memory about it. Let's start by zeroing in on that particular function, and I'm going to start from around line 13455. That's usually a good starting point to get my bearings and see the surrounding context.



38/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 13450,
  "LineCount": 45
}

Results:
{
  "Contents": "13450:\t\tdefault:\n13451:\t\t\treturn in_sleepable(env);\n13452:\t\t}\n13453:\t}\n13454:\t\n13455:\tint bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,\n13456:\t\t\t\t     s32 func_id,\n13457:\t\t\t\t     s16 offset,\n13458:\t\t\t\t     struct bpf_call_arg_meta *meta)\n13459:\t{\n13460:\t\tstruct bpf_kfunc_meta kfunc;\n13461:\t\tint err;\n13462:\t\n13463:\t\tmemset(meta, 0, sizeof(*meta));\n13464:\t\n13465:\t\terr = fetch_kfunc_meta(env, func_id, offset, \u0026kfunc);\n13466:\t\tif (err)\n13467:\t\t\treturn err;\n13468:\t\n13469:\t\tmeta-\u003ebtf = kfunc.btf;\n13470:\t\tmeta-\u003efunc_id = kfunc.id;\n13471:\t\tmeta-\u003efunc_proto = kfunc.proto;\n13472:\t\tmeta-\u003efunc_name = kfunc.name;\n13473:\t\n13474:\t\tif (!kfunc.flags || !btf_kfunc_is_allowed(kfunc.btf, kfunc.id, env-\u003eprog))\n13475:\t\t\treturn -EACCES;\n13476:\t\n13477:\t\tmeta-\u003ekfunc_flags = *kfunc.flags;\n13478:\t\n13479:\t\t/* Only support release referenced argument passed by register */\n13480:\t\tif (is_kfunc_release(meta))\n13481:\t\t\tmeta-\u003erelease_regno = BPF_REG_1;\n13482:\t\n13483:\t\treturn 0;\n13484:\t}\n13485:\t\n13486:\t/*\n13487:\t * Determine how many bytes a helper accesses through a stack pointer at\n13488:\t * argument position @arg (0-based, corresponding to R1-R5).\n13489:\t *\n13490:\t * Returns:\n13491:\t *   \u003e 0   known read access size in bytes\n13492:\t *     0   doesn't read anything directly\n13493:\t * S64_MIN unknown\n13494:\t *   \u003c 0   known write access of (-return) bytes\n"
}

39/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 77426
							output: 22
							thoughts: 66
						

Thoughts:
**My Initial Thoughts on BPF Function Argument Metadata**

Okay, so `bpf_fetch_kfunc_arg_meta` starts by zeroing out the metadata structure, `meta`. That's standard practice, a good first step. But where is the crucial `desc->proto` field getting its initial value? That's what I need to understand. It's the key to understanding how the argument parsing works.

Right, I need to trace back and see how that `desc->proto` is populated. Time to dig into `find_kfunc_desc` and see where `gen_kfunc_arg_proto` is actually being called. Those are the two logical places it would happen. I need to understand the relationship between the function descriptor lookup and the argument prototype generation. Let's see how that connection is made. This is where the magic happens.



40/3 2026/09/11 22:37 tool grepper
0m Args:
{
  "Expression": "gen_kfunc_arg_proto"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=2783=static int fetch_kfunc_meta(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-2845-\nkernel/bpf/verifier.c:2846:static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\nkernel/bpf/verifier.c-2847-\t\t\t       struct bpf_func_proto *proto);\n--\nkernel/bpf/verifier.c=2849=int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)\n--\nkernel/bpf/verifier.c-2963-\nkernel/bpf/verifier.c:2964:\terr = gen_kfunc_arg_proto(env, \u0026meta, \u0026desc-\u003eproto);\nkernel/bpf/verifier.c-2965-\tif (err)\n--\nkernel/bpf/verifier.c=12661=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12854-\nkernel/bpf/verifier.c:12855:static int gen_kfunc_arg_proto(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\nkernel/bpf/verifier.c-12856-\t\t\t       struct bpf_func_proto *proto)\n"
}

41/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 78104
							output: 37
							thoughts: 35
						

Thoughts:
Here's my take on the thought process, summarized as you requested:

**Digging into bpf_add_kfunc_call**

Okay, time to get my hands dirty. I'm going to jump into the `bpf_add_kfunc_call` function. I remember we were looking at that area around line 2964, so that's where I'll start. The main focus is understanding how `desc->proto` gets allocated and initialized within this context. I need to trace back from that specific allocation to grasp the full picture of the dependency and ensure everything is set up correctly for kfunc calls. I need to quickly ascertain how that structure is prepared. I'm hoping I can find the answer pretty quickly. Let's see what's happening.



42/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 2950,
  "LineCount": 35
}

Results:
{
  "Contents": "2950:\t\tmeta.btf = kfunc.btf;\n2951:\t\tmeta.func_id = kfunc.id;\n2952:\t\tmeta.func_proto = kfunc.proto;\n2953:\t\tmeta.func_name = kfunc.name;\n2954:\t\tmeta.kfunc_flags = kfunc.flags ? *kfunc.flags : 0;\n2955:\t\n2956:\t\ttab = krealloc(tab, struct_size(tab, descs, tab-\u003enr_descs + 1), GFP_KERNEL_ACCOUNT);\n2957:\t\tif (!tab)\n2958:\t\t\treturn -ENOMEM;\n2959:\t\tprog_aux-\u003ekfunc_tab = tab;\n2960:\t\n2961:\t\tdesc = \u0026tab-\u003edescs[tab-\u003enr_descs];\n2962:\t\tmemset(desc, 0, sizeof(*desc));\n2963:\t\n2964:\t\terr = gen_kfunc_arg_proto(env, \u0026meta, \u0026desc-\u003eproto);\n2965:\t\tif (err)\n2966:\t\t\treturn err;\n2967:\t\n2968:\t\tdesc-\u003efunc_id = func_id;\n2969:\t\tdesc-\u003eoffset = offset;\n2970:\t\tdesc-\u003eaddr = addr;\n2971:\t\tdesc-\u003efunc_model = func_model;\n2972:\t\ttab-\u003enr_descs++;\n2973:\t\tsort(tab-\u003edescs, tab-\u003enr_descs, sizeof(tab-\u003edescs[0]),\n2974:\t\t     kfunc_desc_cmp_by_id_off, NULL);\n2975:\t\treturn 0;\n2976:\t}\n2977:\t\n2978:\tstatic int add_subprogs(struct bpf_verifier_env *env)\n2979:\t{\n2980:\t\tstruct bpf_subprog_info *subprog = env-\u003esubprog_info;\n2981:\t\tint i, ret, insn_cnt = env-\u003eprog-\u003elen, ex_cb_insn;\n2982:\t\tstruct bpf_insn *insn = env-\u003eprog-\u003einsnsi;\n2983:\t\tconst char *operation, *suggestion;\n2984:\t\n"
}

43/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 78944
							output: 37
							thoughts: 2617
						

Thoughts:
**Deep Dive into Kfunc Argument Verification**

Okay, so I've been tracing the logic for verifying kfunc arguments, and it's starting to crystallize. Initially, `desc` is zeroed, which sets a clean slate. I'm focusing on how arguments that reference BTF IDs are handled, specifically their sizes.

I started by looking into `gen_kfunc_arg_proto` and then zeroing in on `get_kfunc_arg_type`. The `arg_btf_id` pointer is initially set to `NULL`. Later, based on the `arg_type`, we determine whether to set `arg_btf_id` or calculate `arg_size`. `ref_id_ptr` is the pointer inside BTF structure, and `arg_btf_id` actually points into the `meta->btf` structure.

Ah, the union! The `arg_btf_id` and `arg_size` are in a union within `bpf_func_proto`, and that's crucial. Either we use `arg_btf_id` *or* `arg_size`, but never both for the same argument because the argument can be either a pointer to BTF ID or a fixed-size memory region. The `check_func_arg` function neatly highlights this by choosing one to use based on the argument type. `get_kfunc_arg_type` sets either the `arg_size` when it resolves to a fixed size memory, or the `arg_btf_id` when it's just a pointer to the BTF ID.

The real magic happens in `resolve_func_arg_type`. If a kfunc argument resolves to a scalar struct pointer, the argument *type* changes from `ARG_PTR_TO_BTF_ID` to `ARG_PTR_TO_MEM` with `MEM_FIXED_SIZE`. This is when the actual size is looked up, and `arg_size` is populated using `btf_resolve_size`.

This new `arg_size` now becomes the *fixed* size. When the check is performed in `check_func_arg`, it uses the now-populated `arg_size`, not a `NULL` pointer.

Finally, I got to `check_mem_size_reg`. This checks if the size is constant and determines whether to mark the memory region as initialized. I was trying to figure out the use of `meta->arg_raw_mem.regno = 0`, and figured out that the older implementation had `meta = NULL;` as opposed to now.



44/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 7210,
  "LineCount": 40
}

Results:
{
  "Contents": "7210:\t\t */\n7211:\t\tmeta-\u003emsize_max_value = reg_umax(size_reg);\n7212:\t\n7213:\t\t/* The register is SCALAR_VALUE; the access check happens using\n7214:\t\t * its boundaries. For unprivileged variable accesses, disable\n7215:\t\t * raw mode so that the program is required to initialize all\n7216:\t\t * the memory that the helper could just partially fill up.\n7217:\t\t */\n7218:\t\tif (!tnum_is_const(size_reg-\u003evar_off))\n7219:\t\t\tmeta-\u003earg_raw_mem.regno = 0;\n7220:\t\n7221:\t\tif (reg_smin(size_reg) \u003c 0) {\n7222:\t\t\tverbose(env, \"%s min value is negative, either use unsigned or 'var \u0026= const'\\n\",\n7223:\t\t\t\treg_arg_name(env, size_argno));\n7224:\t\t\terr = -EACCES;\n7225:\t\t\tgoto size_error;\n7226:\t\t}\n7227:\t\n7228:\t\tif (reg_umin(size_reg) == 0 \u0026\u0026 !zero_size_allowed) {\n7229:\t\t\tverbose(env, \"%s invalid zero-sized read: u64=[%lld,%lld]\\n\",\n7230:\t\t\t\treg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));\n7231:\t\t\terr = -EACCES;\n7232:\t\t\tgoto size_error;\n7233:\t\t}\n7234:\t\n7235:\t\tif (reg_umax(size_reg) \u003e= BPF_MAX_VAR_SIZ) {\n7236:\t\t\tverbose(env, \"%s unbounded memory access, use 'var \u0026= const' or 'if (var \u003c const)'\\n\",\n7237:\t\t\t\treg_arg_name(env, size_argno));\n7238:\t\t\terr = -EACCES;\n7239:\t\t\tgoto size_error;\n7240:\t\t}\n7241:\t\n7242:\t\tif (access_type \u0026 BPF_READ)\n7243:\t\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n7244:\t\t\t\t\t\t      BPF_READ, zero_size_allowed, meta, NULL);\n7245:\t\tif (!err \u0026\u0026 access_type \u0026 BPF_WRITE)\n7246:\t\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n7247:\t\t\t\t\t\t      BPF_WRITE, zero_size_allowed, meta, NULL);\n7248:\t\tif (err \u0026\u0026 failure)\n7249:\t\t\t*failure = BPF_MEM_SIZE_FAIL_MEMORY;\n"
}

45/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 82913
							output: 35
							thoughts: 119
						

Thoughts:
**Analyzing Memory Access Checks**

Okay, here's what's running through my mind as I dig into this code. I'm focusing on lines 7242-7247 in `check_mem_size_reg`, specifically how the `meta` variable is handled. I see that `meta` is being passed to `check_helper_mem_access`, which immediately raises a flag for me. Previously, the code had a conditional, `if (!tnum_is_const(size_reg->var_off)) meta = NULL;`, which would set `meta` to `NULL` in certain circumstances. So, I need to understand what changed here. The key question is, what does `check_helper_mem_access` actually *do* with the `meta` parameter? I need to get a handle on the interaction between these two functions, so the next step is crystal clear: I need to investigate `check_helper_mem_access` itself to understand how `meta` impacts the memory access verification.



46/3 2026/09/11 22:37 tool grepper
0m Args:
{
  "Expression": "check_helper_mem_access",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=6965=static int check_stack_range_initialized(\n--\nkernel/bpf/verifier.c-7084-\nkernel/bpf/verifier.c:7085:static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\nkernel/bpf/verifier.c-7086-\t\t\t\t   argno_t argno, int access_size,\n--\nkernel/bpf/verifier.c=7190=static int check_mem_size_reg(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-7242-\tif (access_type \u0026 BPF_READ)\nkernel/bpf/verifier.c:7243:\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\nkernel/bpf/verifier.c-7244-\t\t\t\t\t      BPF_READ, zero_size_allowed, meta, NULL);\nkernel/bpf/verifier.c-7245-\tif (!err \u0026\u0026 access_type \u0026 BPF_WRITE)\nkernel/bpf/verifier.c:7246:\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\nkernel/bpf/verifier.c-7247-\t\t\t\t\t      BPF_WRITE, zero_size_allowed, meta, NULL);\n--\nkernel/bpf/verifier.c=7262=static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7285-\tif (access_type \u0026 BPF_READ)\nkernel/bpf/verifier.c:7286:\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,\nkernel/bpf/verifier.c-7287-\t\t\t\t\t      known_memory);\nkernel/bpf/verifier.c-7288-\tif (!err \u0026\u0026 (access_type \u0026 BPF_WRITE))\nkernel/bpf/verifier.c:7289:\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,\nkernel/bpf/verifier.c-7290-\t\t\t\t\t      known_memory);\n--\nkernel/bpf/verifier.c=8888=static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n--\nkernel/bpf/verifier.c-8992-\t\tkey_size = meta-\u003emap.ptr-\u003ekey_size;\nkernel/bpf/verifier.c:8993:\t\terr = check_helper_mem_access(env, reg, argno, key_size, BPF_READ, false, meta,\nkernel/bpf/verifier.c-8994-\t\t\t\t\t      NULL);\n--\nkernel/bpf/verifier.c-9028-\nkernel/bpf/verifier.c:9029:\t\terr = check_helper_mem_access(env, reg, argno, meta-\u003emap.ptr-\u003evalue_size,\nkernel/bpf/verifier.c-9030-\t\t\t\t\t      arg_type \u0026 MEM_WRITE ? BPF_WRITE : BPF_READ,\n--\nkernel/bpf/verifier.c=12661=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12802-\t\t * Otherwise this is a fixed-size memory buffer supported by\nkernel/bpf/verifier.c:12803:\t\t * check_helper_mem_access(): a pointer to a scalar or a struct of\nkernel/bpf/verifier.c-12804-\t\t * scalars. The access size is derived from the pointed-to BTF type.\n"
}

47/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 84281
							output: 37
							thoughts: 29
						

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

**Analyzing `check_helper_mem_access` for `meta` Usage**

Alright, let's dive into this. My initial instinct is to zero in on that `check_helper_mem_access` function, specifically around line 7085, to understand how the `meta` variable is being utilized within its logic. I'm assuming, given the context, that `meta` likely holds crucial metadata related to a memory access operation. I'm keen to dissect the control flow within that section of code to determine the exact role of `meta`. Is it being used for permission checks, address translation, size validation, or perhaps something more nuanced and application-specific? I'm anticipating to see how `meta` interacts with the underlying hardware or memory management structures. I need to understand whether this is a general-purpose access check or one optimized for specific data structures. The surrounding code will provide the key.



48/3 2026/09/11 22:37 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 7085,
  "LineCount": 50
}

Results:
{
  "Contents": "7085:\tstatic int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n7086:\t\t\t\t\t   argno_t argno, int access_size,\n7087:\t\t\t\t\t   enum bpf_access_type access_type, bool zero_size_allowed,\n7088:\t\t\t\t\t   struct bpf_call_arg_meta *meta, bool *known_memory)\n7089:\t{\n7090:\t\tstruct bpf_reg_state *regs = cur_regs(env);\n7091:\t\tu32 *max_access;\n7092:\t\n7093:\t\tif (known_memory)\n7094:\t\t\t*known_memory = true;\n7095:\t\n7096:\t\tswitch (base_type(reg-\u003etype)) {\n7097:\t\tcase PTR_TO_PACKET:\n7098:\t\tcase PTR_TO_PACKET_META:\n7099:\t\t\tif (!may_access_direct_pkt_data(env, meta, access_type)) {\n7100:\t\t\t\tverbose(env, \"function access to the packet is not allowed\\n\");\n7101:\t\t\t\treturn -EACCES;\n7102:\t\t\t}\n7103:\t\t\treturn check_packet_access(env, reg, argno, 0, access_size,\n7104:\t\t\t\t\t\t   zero_size_allowed);\n7105:\t\tcase PTR_TO_MAP_KEY:\n7106:\t\t\tif (access_type == BPF_WRITE) {\n7107:\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n7108:\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n7109:\t\t\t\treturn -EACCES;\n7110:\t\t\t}\n7111:\t\t\treturn check_mem_region_access(env, reg, argno, 0, access_size,\n7112:\t\t\t\t\t\t       reg-\u003emap_ptr-\u003ekey_size, false);\n7113:\t\tcase PTR_TO_MAP_VALUE:\n7114:\t\t\tif (check_map_access_type(env, reg, 0, access_size, access_type))\n7115:\t\t\t\treturn -EACCES;\n7116:\t\t\treturn check_map_access(env, reg, argno, 0, access_size,\n7117:\t\t\t\t\t\tzero_size_allowed, ACCESS_HELPER);\n7118:\t\tcase PTR_TO_MEM:\n7119:\t\t\tif (type_is_rdonly_mem(reg-\u003etype)) {\n7120:\t\t\t\tif (access_type == BPF_WRITE) {\n7121:\t\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n7122:\t\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n7123:\t\t\t\t\treturn -EACCES;\n7124:\t\t\t\t}\n7125:\t\t\t}\n7126:\t\t\treturn check_mem_region_access(env, reg, argno, 0,\n7127:\t\t\t\t\t\t       access_size, reg-\u003emem_size,\n7128:\t\t\t\t\t\t       zero_size_allowed);\n7129:\t\tcase PTR_TO_BUF:\n7130:\t\t\tif (type_is_rdonly_mem(reg-\u003etype)) {\n7131:\t\t\t\tif (access_type == BPF_WRITE) {\n7132:\t\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n7133:\t\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n7134:\t\t\t\t\treturn -EACCES;\n"
}

49/3 2026/09/11 22:37 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 85508
							output: 37
							thoughts: 30
						

Thoughts:
Okay, let's dive into this. I need to understand what's happening within the `check_helper_mem_access` function, specifically from line 7135 to 7190. I know this is a critical section of code, likely dealing with memory access validation for helper functions, and I'm keen to get a good handle on its inner workings. I need to refresh my understanding of the flow, the conditions being checked, and how it all contributes to overall system stability and security. It's time to put on my investigator hat.



50/3 2026/09/11 22:38 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 7135,
  "LineCount": 55
}

Results:
{
  "Contents": "7135:\t\t\t\t}\n7136:\t\n7137:\t\t\t\tmax_access = \u0026env-\u003eprog-\u003eaux-\u003emax_rdonly_access;\n7138:\t\t\t} else {\n7139:\t\t\t\tmax_access = \u0026env-\u003eprog-\u003eaux-\u003emax_rdwr_access;\n7140:\t\t\t}\n7141:\t\t\treturn check_buffer_access(env, reg, argno, 0,\n7142:\t\t\t\t\t\t   access_size, zero_size_allowed,\n7143:\t\t\t\t\t\t   max_access);\n7144:\t\tcase PTR_TO_STACK:\n7145:\t\t\treturn check_stack_range_initialized(\n7146:\t\t\t\t\tenv, reg,\n7147:\t\t\t\t\targno, 0, access_size,\n7148:\t\t\t\t\tzero_size_allowed, access_type, meta);\n7149:\t\tcase PTR_TO_BTF_ID:\n7150:\t\t\treturn check_ptr_to_btf_access(env, regs, reg, argno, 0,\n7151:\t\t\t\t\t\t       access_size, access_type, -1);\n7152:\t\tcase PTR_TO_CTX:\n7153:\t\t\t/* Only permit reading or writing syscall context using helper calls. */\n7154:\t\t\tif (is_var_ctx_off_allowed(env-\u003eprog)) {\n7155:\t\t\t\tint err = check_mem_region_access(env, reg, argno, 0, access_size, U16_MAX,\n7156:\t\t\t\t\t\t\t\t  zero_size_allowed);\n7157:\t\t\t\tif (err)\n7158:\t\t\t\t\treturn err;\n7159:\t\t\t\tif (env-\u003eprog-\u003eaux-\u003emax_ctx_offset \u003c reg_umax(reg) + access_size)\n7160:\t\t\t\t\tenv-\u003eprog-\u003eaux-\u003emax_ctx_offset = reg_umax(reg) + access_size;\n7161:\t\t\t\treturn 0;\n7162:\t\t\t}\n7163:\t\t\tfallthrough;\n7164:\t\tdefault: /* scalar_value or invalid ptr */\n7165:\t\t\t/* Allow zero-byte read from NULL, regardless of pointer type */\n7166:\t\t\tif (zero_size_allowed \u0026\u0026 access_size == 0 \u0026\u0026\n7167:\t\t\t    bpf_register_is_null(reg))\n7168:\t\t\t\treturn 0;\n7169:\t\t\tif (known_memory \u0026\u0026 base_type(reg-\u003etype) != PTR_TO_CTX)\n7170:\t\t\t\t*known_memory = false;\n7171:\t\n7172:\t\t\tverbose(env, \"%s type=%s \", reg_arg_name(env, argno),\n7173:\t\t\t\treg_type_str(env, reg-\u003etype));\n7174:\t\t\tverbose(env, \"expected=%s\\n\", reg_type_str(env, PTR_TO_STACK));\n7175:\t\t\treturn -EACCES;\n7176:\t\t}\n7177:\t}\n7178:\t\n7179:\tenum bpf_mem_size_failure {\n7180:\t\tBPF_MEM_SIZE_FAIL_NONE,\n7181:\t\tBPF_MEM_SIZE_FAIL_MEMORY,\n7182:\t\tBPF_MEM_SIZE_FAIL_SIZE,\n7183:\t};\n7184:\t\n7185:\t/* verify arguments to helpers or kfuncs consisting of a pointer and an access\n7186:\t * size.\n7187:\t *\n7188:\t * @mem_reg contains the pointer, @size_reg contains the access size.\n7189:\t */\n"
}

51/3 2026/09/11 22:38 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 86625
							output: 35
							thoughts: 135
						

Thoughts:
**My Analysis of Memory Access Checks**

Okay, let me break this down. I'm seeing that the `check_helper_mem_access` function relies heavily on this `meta` structure. Specifically, it's passing `meta` into both `may_access_direct_pkt_data(env, meta, access_type)` and `check_stack_range_initialized(..., meta)`. So, `meta` seems to be a key piece of contextual information for these access checks.

Now, looking at `check_mem_size_reg`, it's interesting. I see that if the memory size isn't constant, it sets `meta->arg_raw_mem.regno = 0;`. This seems to be disabling "raw mode," but crucially, it *keeps* `meta` valid! The fact that `meta` is still valid is important because that means `may_access_direct_pkt_data` and likely other functions down the line, can continue to use the information within it. We're carefully preserving the context even after disabling raw mode.

My next point of analysis will be centered around `check_stack_range_initialized`. I need to dig deeper into *how* this function is using `meta` specifically. That will be a key piece of understanding how the entire memory access mechanism works.



52/3 2026/09/11 22:38 tool grepper
0m Args:
{
  "Expression": "check_stack_range_initialized",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=4081=enum bpf_access_src {\n--\nkernel/bpf/verifier.c-4085-\nkernel/bpf/verifier.c:4086:static int check_stack_range_initialized(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\nkernel/bpf/verifier.c-4087-\t\t\t\t\t argno_t argno, int off, int access_size,\n--\nkernel/bpf/verifier.c=4110=static int check_stack_read_var_off(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-4118-\t */\nkernel/bpf/verifier.c:4119:\terr = check_stack_range_initialized(env, reg, ptr_argno, off, size,\nkernel/bpf/verifier.c-4120-\t\t\t\t\t    false, BPF_READ, NULL);\n--\nkernel/bpf/verifier.c=6922=static int check_atomic(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-6964- */\nkernel/bpf/verifier.c:6965:static int check_stack_range_initialized(\nkernel/bpf/verifier.c-6966-\t\tstruct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int off,\n--\nkernel/bpf/verifier.c=7085=static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7144-\tcase PTR_TO_STACK:\nkernel/bpf/verifier.c:7145:\t\treturn check_stack_range_initialized(\nkernel/bpf/verifier.c-7146-\t\t\t\tenv, reg,\n--\nkernel/bpf/verifier.c=8154=static bool arg_type_is_raw_mem(enum bpf_arg_type type)\n--\nkernel/bpf/verifier.c-8158-\t * (uninitialized) memory argument, and like ARG_PTR_TO_MEM it may be\nkernel/bpf/verifier.c:8159:\t * passed as a PTR_TO_STACK that reaches check_stack_range_initialized().\nkernel/bpf/verifier.c-8160-\t */\n"
}

53/3 2026/09/11 22:38 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 87717
							output: 37
							thoughts: 29
						

Thoughts:
Here's my summary, as if I were thinking these thoughts:

**Analyzing `check_stack_range_initialized`**

Okay, let's dive into that `check_stack_range_initialized` function. It seems to be around line 6965, which probably means it's pretty important in this context. My main interest here is how it's handling `meta`. This variable is critical, so understanding its role in the initialization check will illuminate the overall process and likely expose potential vulnerabilities or optimizations. I need to figure out what data from `meta` this function is using, how it's using it, and what that implies about the state of the stack range. I'm looking for the logic that ensures the stack range is, in fact, properly initialized.



54/3 2026/09/11 22:38 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6965,
  "LineCount": 55
}

Results:
{
  "Contents": "6965:\tstatic int check_stack_range_initialized(\n6966:\t\t\tstruct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int off,\n6967:\t\t\tint access_size, bool zero_size_allowed,\n6968:\t\t\tenum bpf_access_type type, struct bpf_call_arg_meta *meta)\n6969:\t{\n6970:\t\tstruct bpf_func_state *state = bpf_func(env, reg);\n6971:\t\tint err, min_off, max_off, i, j, slot, spi;\n6972:\t\t/* Some accesses can write anything into the stack, others are\n6973:\t\t * read-only.\n6974:\t\t */\n6975:\t\tbool clobber = type == BPF_WRITE;\n6976:\t\t/*\n6977:\t\t * Negative access_size signals global subprog arg check where\n6978:\t\t * STACK_POISON slots are acceptable. static stack liveness\n6979:\t\t * might have determined that subprog doesn't read them,\n6980:\t\t * but BTF based global subprog validation isn't accurate enough.\n6981:\t\t */\n6982:\t\tbool allow_poison = access_size \u003c 0 || clobber;\n6983:\t\t/* The call will initialize the memory; uninitialized stack allowed */\n6984:\t\tbool raw_mode = meta \u0026\u0026 meta-\u003earg_raw_mem.regno == reg_from_argno(argno);\n6985:\t\n6986:\t\taccess_size = abs(access_size);\n6987:\t\n6988:\t\tif (access_size == 0 \u0026\u0026 !zero_size_allowed) {\n6989:\t\t\tverbose(env, \"invalid zero-sized read\\n\");\n6990:\t\t\treturn -EACCES;\n6991:\t\t}\n6992:\t\n6993:\t\terr = check_stack_access_within_bounds(env, reg, argno, off, access_size, type);\n6994:\t\tif (err)\n6995:\t\t\treturn err;\n6996:\t\n6997:\t\tif (tnum_is_const(reg-\u003evar_off)) {\n6998:\t\t\tmin_off = max_off = reg-\u003evar_off.value + off;\n6999:\t\t} else {\n7000:\t\t\t/* Variable offset is prohibited for unprivileged mode for\n7001:\t\t\t * simplicity since it requires corresponding support in\n7002:\t\t\t * Spectre masking for stack ALU.\n7003:\t\t\t * See also retrieve_ptr_limit().\n7004:\t\t\t */\n7005:\t\t\tif (!env-\u003ebypass_spec_v1) {\n7006:\t\t\t\tchar tn_buf[48];\n7007:\t\n7008:\t\t\t\ttnum_strn(tn_buf, sizeof(tn_buf), reg-\u003evar_off);\n7009:\t\t\t\tverbose(env, \"%s variable offset stack access prohibited for !root, var_off=%s\\n\",\n7010:\t\t\t\t\treg_arg_name(env, argno), tn_buf);\n7011:\t\t\t\treturn -EACCES;\n7012:\t\t\t}\n7013:\t\t\t/* Only initialized buffer on stack is allowed to be accessed\n7014:\t\t\t * with variable offset. With uninitialized buffer it's hard to\n7015:\t\t\t * guarantee that whole memory is marked as initialized on\n7016:\t\t\t * helper return since specific bounds are unknown what may\n7017:\t\t\t * cause uninitialized stack leaking.\n7018:\t\t\t */\n7019:\t\t\traw_mode = false;\n"
}

55/3 2026/09/11 22:38 llm kmsan-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 88901
							output: 32
							thoughts: 1058
						

Thoughts:
**Analyzing BPF Verifier Changes: Addressing `raw_mode`, Packet Data Access, and Kfunc Argument Handling**

Okay, let's break down these BPF verifier modifications. First, I'm zeroing in on line 6984, the `raw_mode` logic. The conditional `bool raw_mode = meta && meta->arg_raw_mem.regno == reg_from_argno(argno);` presents an optimization opportunity. If `meta->arg_raw_mem.regno` is set to `0`, `raw_mode` becomes false, which achieves the same outcome as if `meta` were `NULL`. This is significant because, instead of nulling `meta`, we are selectively setting a flag, improving the control flow, potentially leading to fewer checks down the line, and allowing us to keep `meta` for other checks.

Next, I'm examining the `may_access_direct_pkt_data` function, specifically how packet data access is handled. The diff reveals a subtle but important change in how `env->seen_direct_write` is updated. Notably, the introduction of a check for `meta && !meta->btf && meta->func_id` is crucial for LWT_XMIT, SK_SKB, and SK_MSG program types. The addition of the selftest `test_verifier_kfunc_packet_access` further clarifies the intent: the verifier must correctly recognize when a kfunc writes to packet data, ensuring that `env->seen_direct_write` is set (when `t == BPF_WRITE`). This sets in motion the mechanism to unclone the skb via `bpf_unclone_prologue()`.

Finally, the focus shifts to `bpf_do_misc_fixups`, specifically the adjustments related to handling program auxiliary data for timer callbacks. The original code used `BPF_LD_IMM64(BPF_REG_3, (long)prog->aux)`, but this has been altered. Now, we're using a helper:  `aux = &env->insn_aux_data[i + delta];`, and `BPF_LD_IMM64(aux->arg_prog, (long)prog->aux)`, combined with updates to `check_func_arg`, and `bpf_timer_set_callback_proto`. These changes introduce a new argument type `ARG_PTR_TO_PROG_AUX`, connecting register state management more directly with timer callbacks. The use of `aux->arg_prog`, seems to be a mechanism for associating the program's auxiliary data (`prog->aux`) with the correct register within the BPF instruction stream, especially for functions that deal with bpf timer callback functions. Now I need to see how `aux->arg_prog` was used before this patch to establish the before state.



56/3 2026/09/11 22:39 tool grepper
0m Args:
{
  "Expression": "arg_prog",
  "PathPrefix": "kernel/bpf/fixups.c"
}

Results:
{
  "Output": "kernel/bpf/fixups.c=1540=int bpf_do_misc_fixups(struct bpf_verifier_env *env)\n--\nkernel/bpf/fixups.c-2023-\t\taux = \u0026env-\u003einsn_aux_data[i + delta];\nkernel/bpf/fixups.c:2024:\t\tif (aux-\u003earg_prog) {\nkernel/bpf/fixups.c-2025-\t\t\t/* The verifier will process callback_fn as many times as necessary\n--\nkernel/bpf/fixups.c-2038-\t\t\tstruct bpf_insn ld_addrs[2] = {\nkernel/bpf/fixups.c:2039:\t\t\t\tBPF_LD_IMM64(aux-\u003earg_prog, (long)prog-\u003eaux),\nkernel/bpf/fixups.c-2040-\t\t\t};\n"
}

57/3 2026/09/11 22:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 90836
							output: 31
							thoughts: 2984
						

Thoughts:
**Analyzing BPF Verifier Changes for Uninitialized Memory Risks**

Okay, let's break this down. We're looking at a patch series that significantly refactors how BPF programs handle arguments, particularly related to BTF (BPF Type Format) and the `prog->aux` argument, and need to determine if it introduces any uninitialized memory risks detectable by KMSAN.  Specifically, we want to know if KMSAN=true is needed.

First, the original code had a hardcoded `BPF_LD_IMM64` that has been changed, using `ARG_PTR_TO_PROG_AUX` to allow helpers and kfuncs to properly use `prog->aux`. The verifier now checks for the correct usage of `aux->arg_prog` or `ARG_PTR_TO_PROG_AUX`, using `check_arg_prog_aux`, called from `check_func_proto` and `gen_kfunc_arg_proto`. This refactoring seems safe on its own.

Now, we see that `check_func_arg_nullability` and `check_func_arg_release` are new checks to ensure pointer safety, and the code snippet for `percpu_alloc_fail.c` exposed a possible NULL pointer issue. This means we have a bug fix for an issue where the `reg->id` could be lost when `reg->type` was nullable, due to a previous incorrect clear of the register id.  This looks like a correctness issue and an improvement, so on its own, seems safe with respect to uninitialized memory.

Next, a change to how `check_func_arg` is handled and that has been broken down to functions to check for nullability, type, register offsets, and release.

Now we are looking at `check_reg_type` which has a significant change involving `ARG_PTR_TO_BTF_ID` and a new `process_arg_ptr_to_btf_id` function. The old large switch was removed.  We're going to examine `check_func_arg` and `process_arg_ptr_to_btf_id` more closely for possible KMSAN concerns.

We need to review the KMSAN criteria: kernel structures to userspace, conditional logic on uninitialized variables, allocation and initialization of complex data structures with potentially uninitialized fields, or allowing access to uninitialized bytes of existing buffers.  We *don't* want to mark it as needing KMSAN if the changes are about other kinds of bugs (out-of-bounds, use-after-free, etc.).

We look at `btf_check_func_arg_match`, `check_func_arg`, and `process_arg_ptr_to_btf_id` and have to analyze the variables.

1.  In `btf_check_func_arg_match`, `meta` is initialized with `memset(&meta, 0, sizeof(meta))`. That looks good.

2.  Within `check_func_arg`, all local variables appear properly initialized.

3.  In `check_func_arg` in `case ARG_PTR_TO_MEM:`, we need to check if `check_mem_reg` correctly handles `known_memory`. This needs further investigation.

So, *currently*, the code changes in the path series seem to improve existing checks for NULL pointers and the refactoring looks safe. The variables are, for the most part, initialized before use.
The changes, so far, do *not* introduce or modify:
    - 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.
    - Conditional logic or branching that depends on potentially uninitialized variables or struct fields.
    - Allocation or initialization of complex data structures where uninitialized fields could be read later in reachable code paths.
    - Bounds checks, lengths, or logic in a way that allows surrounding code to access uninitialized bytes of existing buffers.

We still need to confirm the handling of the `known_memory` flag in `check_mem_reg` to be absolutely certain. If this is handled properly, there should be no uninitialized memory risks. Let's dig deeper into `check_mem_reg`.



58/3 2026/09/11 22:39 tool grepper
0m Args:
{
  "Expression": "known_memory",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=7085=static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-7087-\t\t\t\t   enum bpf_access_type access_type, bool zero_size_allowed,\nkernel/bpf/verifier.c:7088:\t\t\t\t   struct bpf_call_arg_meta *meta, bool *known_memory)\nkernel/bpf/verifier.c-7089-{\n--\nkernel/bpf/verifier.c-7092-\nkernel/bpf/verifier.c:7093:\tif (known_memory)\nkernel/bpf/verifier.c:7094:\t\t*known_memory = true;\nkernel/bpf/verifier.c-7095-\n--\nkernel/bpf/verifier.c-7168-\t\t\treturn 0;\nkernel/bpf/verifier.c:7169:\t\tif (known_memory \u0026\u0026 base_type(reg-\u003etype) != PTR_TO_CTX)\nkernel/bpf/verifier.c:7170:\t\t\t*known_memory = false;\nkernel/bpf/verifier.c-7171-\n--\nkernel/bpf/verifier.c=7262=static int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\nkernel/bpf/verifier.c-7263-\t\t\t argno_t argno, u32 mem_size, enum bpf_access_type access_type,\nkernel/bpf/verifier.c:7264:\t\t\t struct bpf_call_arg_meta *meta, bool *known_memory)\nkernel/bpf/verifier.c-7265-{\n--\nkernel/bpf/verifier.c-7269-\t\treturn mark_arg_precision(env, argno);\nkernel/bpf/verifier.c:7270:\tif (known_memory)\nkernel/bpf/verifier.c:7271:\t\t*known_memory = true;\nkernel/bpf/verifier.c-7272-\n--\nkernel/bpf/verifier.c-7286-\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_READ, true, meta,\nkernel/bpf/verifier.c:7287:\t\t\t\t\t      known_memory);\nkernel/bpf/verifier.c-7288-\tif (!err \u0026\u0026 (access_type \u0026 BPF_WRITE))\nkernel/bpf/verifier.c-7289-\t\terr = check_helper_mem_access(env, reg, argno, size, BPF_WRITE, true, meta,\nkernel/bpf/verifier.c:7290:\t\t\t\t\t      known_memory);\nkernel/bpf/verifier.c-7291-\n--\nkernel/bpf/verifier.c=8888=static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n--\nkernel/bpf/verifier.c-9176-\t\tenum bpf_access_type access_type;\nkernel/bpf/verifier.c:9177:\t\tbool known_memory;\nkernel/bpf/verifier.c-9178-\n--\nkernel/bpf/verifier.c-9188-\nkernel/bpf/verifier.c:9189:\t\terr = check_mem_reg(env, reg, argno, arg_size, access_type, meta, \u0026known_memory);\nkernel/bpf/verifier.c-9190-\t\tif (err \u003c 0) {\nkernel/bpf/verifier.c:9191:\t\t\tif (known_memory)\nkernel/bpf/verifier.c-9192-\t\t\t\tbpf_diag_call_arg_fmt(env, insn_idx, argno, meta-\u003efunc_name,\n"
}

59/3 2026/09/11 22:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 95769
							output: 326
							thoughts: 4840
						

Thoughts:
**Analysis of BPF Verifier Refactoring for KMSAN Applicability**

Okay, I've gone through this patch and its potential impact, and here's my breakdown. The core of this patch is refactoring and consolidating how the BPF verifier handles arguments for both helpers and kfuncs. I've focused on whether these changes could introduce or expose uninitialized memory issues that KMSAN is specifically designed to catch.

First, I examined `check_mem_reg`, noting the potential early return if `bpf_register_is_null(reg)` is true. I confirmed the logic prevents `known_memory` from being set in this scenario, as `mark_arg_precision` is called instead, and that this logic mirrors a previous implementation. I then looked at `check_mem_size_reg`, confirming `*failure` is always initialized on error paths. Next, I studied `check_func_args`, and recognized it now utilizes a more consistent approach across helpers and kfuncs by ensuring the argument types are properly handled. The loop boundaries for argument checks are controlled by either `MAX_BPF_FUNC_REG_ARGS` (for helpers) or `btf_type_vlen` (for kfuncs), and unused arguments are handled correctly. Furthermore, I checked the helper to kfunc conversion to check there were no leaks.

I carefully scrutinized the code for any data copying to userspace and found none, eliminating that concern. The most critical part, checking for reads of uninitialized memory or changes in memory tracking, included several specific checks. The switch from `ARG_ANYTHING` to `ARG_SCALAR` was reviewed and confirmed to prevent passing pointers where scalars are expected. I verified that `check_reg_arg`, `check_func_arg_reg_off`, `check_helper_mem_access`, `check_stack_range_initialized`, `process_arg_ptr_to_btf_id`, and `check_func_arg_release` all ensure registers and memory are initialized before use, thereby ensuring there are no uninitialized variables being read. I also checked potential uninitialized variable issues due to `ARG_PTR_TO_BTF_ID` and determined there are no issues. Finally, I confirmed that a diagnostic string return is always safe.

In summary, this patch is a verifier refactor and does not introduce or expose risks of uninitialized memory being read. The changes primarily involve consolidating and improving existing verifier logic, which could result in bugs of invalid BPF program rejection, program crashes, type mismatches, or NULL derefs, but these are all standard verifier errors detected by KASAN or standard verifier testing. Because these changes do not introduce or expose risks detectable only by KMSAN, KMSAN is not needed here; standard KASAN and existing sanitizers/debug tools are the appropriate detectors.



60/3 2026/09/11 22:39 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series refactors the BPF verifier's argument-checking infrastructure by unifying helper, kfunc, and subprog argument verification paths into common functions (check_func_args, check_func_arg, check_func_arg_nullability, check_func_arg_release, etc.) and extending enum bpf_arg_type to encompass kfunc argument types. It also updates diagnostic formatting, skb packet access tracking for kfuncs, and misc fixups for prog_aux passing.\n\nAll stack-allocated and heap-allocated data structures introduced or modified in the patch (such as struct bpf_call_arg_meta, struct bpf_func_proto, and helper/kfunc descriptor records) are explicitly initialized using memset() or field assignments prior to access. The changes do not introduce or alter any kernel data copied to user space, netlink messages, socket options, or ioctl outputs. Nor do they introduce conditional branches on uninitialized kernel variables or expose uninitialized buffer regions to reads.\n\nPotential bugs introduced by this refactoring would be verifier logic errors (such as register type mismatches, safety checks, or potential out-of-bounds packet/memory accesses in BPF programs), which are detectable by KASAN, static analysis, or standard verifier test suites. Because there are no uninitialized memory access risks or info-leaks specific to KMSAN, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series refactors the BPF verifier's argument-checking infrastructure by unifying helper, kfunc, and subprog argument verification paths into common functions (check_func_args, check_func_arg, check_func_arg_nullability, check_func_arg_release, etc.) and extending enum bpf_arg_type to encompass kfunc argument types. It also updates diagnostic formatting, skb packet access tracking for kfuncs, and misc fixups for prog_aux passing.\n\nAll stack-allocated and heap-allocated data structures introduced or modified in the patch (such as struct bpf_call_arg_meta, struct bpf_func_proto, and helper/kfunc descriptor records) are explicitly initialized using memset() or field assignments prior to access. The changes do not introduce or alter any kernel data copied to user space, netlink messages, socket options, or ioctl outputs. Nor do they introduce conditional branches on uninitialized kernel variables or expose uninitialized buffer regions to reads.\n\nPotential bugs introduced by this refactoring would be verifier logic errors (such as register type mismatches, safety checks, or potential out-of-bounds packet/memory accesses in BPF programs), which are detectable by KASAN, static analysis, or standard verifier test suites. Because there are no uninitialized memory access risks or info-leaks specific to KMSAN, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

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