AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/11 11:30 flow patch-triage
4m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_EVENTS"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "copy_remote_mm_str"
  ],
  "KMSANReasoning": "The patch introduces two new BPF kfuncs: `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`, which copy data from a user address space into a kernel buffer (`dst`). These kfuncs are available to privileged BPF programs (e.g., tracing or LSM programs). For privileged programs, the BPF verifier allows passing uninitialized kernel stack memory to kfuncs and assumes the kfunc will fully initialize the buffer (marking the entire buffer as initialized in the verifier state).\n\nHowever, `bpf_copy_from_user_mm_str` only fully initializes the `dst` buffer if the `BPF_F_PAD_ZEROS` flag is provided. If this flag is omitted, the function only initializes the buffer up to the length of the copied string (plus the NUL terminator), leaving the remainder of the buffer uninitialized. Because the verifier incorrectly marks the entire buffer as initialized, a BPF program can read the uninitialized tail of the buffer and leak kernel stack memory to userspace (e.g., via BPF maps).\n\nStandard KASAN cannot detect this issue because the memory accesses are strictly within the bounds of the allocated stack buffer. KMSAN is specifically designed to track uninitialized memory at runtime and will reliably detect this kernel info-leak when the BPF program reads the uninitialized bytes. Therefore, a dedicated KMSAN fuzzing session is highly justified.",
  "NeedsKMSAN": true,
  "Reasoning": "The patch introduces new BPF kfuncs `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` to allow BPF programs to copy data from a specific user address space (`struct mm_struct`). These functions are reachable from BPF programs and are worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/11 11:30 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 28654060c71ec6347349db4bcee1f3e5fec2ca06\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 11:30:38 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/mm.h b/include/linux/mm.h\nindex 485df9c2dbddb..eede435bf4a3f 100644\n--- a/include/linux/mm.h\n+++ b/include/linux/mm.h\n@@ -3222,6 +3222,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,\n \t\tvoid *buf, int len, unsigned int gup_flags);\n \n #ifdef CONFIG_BPF_SYSCALL\n+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n+\t\t\t      void *buf, int len, unsigned int gup_flags);\n extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n \t\t\t      void *buf, int len, unsigned int gup_flags);\n #endif\ndiff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c\nindex 6388b6b23e490..9ba3362832cd0 100644\n--- a/kernel/bpf/helpers.c\n+++ b/kernel/bpf/helpers.c\n@@ -4931,6 +4931,8 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_local_irq_save)\n BTF_ID_FLAGS(func, bpf_local_irq_restore)\n #ifdef CONFIG_BPF_EVENTS\n+BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)\n BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)\n BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex add3affc57035..8bdcb65618d01 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -5682,6 +5682,10 @@ BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {\n \tstruct inode *d_inode;\n };\n \n+BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm) {\n+\tstruct mm_struct *mm;\n+};\n+\n BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket) {\n \tstruct sock *sk;\n };\n@@ -5732,6 +5736,7 @@ static bool type_is_trusted_or_null(struct bpf_verifier_env *env,\n {\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));\n+\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm));\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));\n \n \treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id,\ndiff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c\nindex 891897f8a1b35..8931b3b2fc9d7 100644\n--- a/kernel/trace/bpf_trace.c\n+++ b/kernel/trace/bpf_trace.c\n@@ -24,6 +24,7 @@\n #include \u003clinux/key.h\u003e\n #include \u003clinux/namei.h\u003e\n #include \u003clinux/file.h\u003e\n+#include \u003clinux/mm.h\u003e\n \n #include \u003cnet/bpf_sk_storage.h\u003e\n \n@@ -3619,6 +3620,88 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid\n \treturn bpf_send_signal_common(sig, type, task, value);\n }\n \n+/**\n+ * bpf_copy_from_user_mm() - Copy data from an address space\n+ * @dst:             Destination address, in kernel space\n+ * @dst__sz:         Number of bytes to copy\n+ * @unsafe_ptr__ign: Source address in the address space\n+ * @mm:              Address space to copy from\n+ * @flags:           Reserved for future use; must be zero\n+ *\n+ * Copies data from the user address space associated with @mm. The destination\n+ * is zeroed if the requested number of bytes cannot be copied in full.\n+ *\n+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy\n+ * fails or is partial.\n+ */\n+__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\n+\t\t\t\t      const void __user *unsafe_ptr__ign,\n+\t\t\t\t      struct mm_struct *mm, u64 flags)\n+{\n+\tint ret;\n+\n+\tif (unlikely(flags))\n+\t\treturn -EINVAL;\n+\n+\tif (unlikely(!dst__sz))\n+\t\treturn 0;\n+\n+\tret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,\n+\t\t\t       dst, dst__sz, 0);\n+\tif (ret == dst__sz)\n+\t\treturn 0;\n+\n+\tmemset(dst, 0, dst__sz);\n+\treturn ret \u003c 0 ? ret : -EFAULT;\n+}\n+\n+/**\n+ * bpf_copy_from_user_mm_str() - Copy a string from an address space\n+ * @dst:             Destination address, in kernel space. This buffer must be\n+ *                   at least @dst__sz bytes long\n+ * @dst__sz:         Maximum number of bytes to copy, including the trailing NUL\n+ * @unsafe_ptr__ign: Source address in the address space\n+ * @mm:              Address space to copy from\n+ * @flags:           The only supported flag is BPF_F_PAD_ZEROS\n+ *\n+ * Copies a NUL-terminated string from the user address space associated with\n+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz\n+ * is zero.\n+ *\n+ * If BPF_F_PAD_ZEROS is set, the unused portion of @dst is cleared on success\n+ * and all of @dst is cleared on failure.\n+ *\n+ * Return: The number of copied bytes including the NUL terminator on success,\n+ * or a negative error code on failure.\n+ */\n+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,\n+\t\t\t\t\t  const void __user *unsafe_ptr__ign,\n+\t\t\t\t\t  struct mm_struct *mm, u64 flags)\n+{\n+\tint ret;\n+\n+\tif (unlikely(flags \u0026 ~BPF_F_PAD_ZEROS))\n+\t\treturn -EINVAL;\n+\n+\tif (unlikely(!dst__sz))\n+\t\treturn 0;\n+\n+\tret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,\n+\t\t\t\t dst, dst__sz, 0);\n+\tif (ret \u003c 0)\n+\t\tgoto err;\n+\n+\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\tmemset(dst + ret, 0, dst__sz - ret);\n+\n+\treturn ret + 1;\n+\n+err:\n+\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\tmemset(dst, 0, dst__sz);\n+\treturn ret;\n+}\n+\n __bpf_kfunc int bpf_probe_read_user_dynptr(const struct bpf_dynptr *dptr, u64 off,\n \t\t\t\t\t   u64 size, const void __user *unsafe_ptr__ign)\n {\ndiff --git a/mm/memory.c b/mm/memory.c\nindex 6b8280cfc1dbe..03cbd3b02befb 100644\n--- a/mm/memory.c\n+++ b/mm/memory.c\n@@ -7218,6 +7218,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n \treturn buf - old_buf;\n }\n \n+/**\n+ * copy_remote_mm_str - copy a string from a remote address space.\n+ * @mm:         the remote address space\n+ * @addr:       start address to read from\n+ * @buf:        destination buffer\n+ * @len:        number of bytes to copy\n+ * @gup_flags:  flags modifying lookup behaviour\n+ *\n+ * The caller must hold a reference on @mm.\n+ *\n+ * Return: number of bytes copied from @addr (source) to @buf (destination),\n+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.\n+ * On any error, return -EFAULT.\n+ */\n+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n+\t\t       void *buf, int len, unsigned int gup_flags)\n+{\n+\tif (unlikely(len == 0))\n+\t\treturn 0;\n+\n+\treturn __copy_remote_vm_str(mm, addr, buf, len, gup_flags);\n+}\n+\n /**\n  * copy_remote_vm_str - copy a string from another process's address space.\n  * @tsk:\tthe task of the target address space\n@@ -7247,7 +7270,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n \t\treturn -EFAULT;\n \t}\n \n-\tret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);\n+\tret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);\n \n \tmmput(mm);\n \ndiff --git a/mm/nommu.c b/mm/nommu.c\nindex ed3934bc2de48..8fbe6306d0746 100644\n--- a/mm/nommu.c\n+++ b/mm/nommu.c\n@@ -1752,6 +1752,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n \treturn ret;\n }\n \n+/**\n+ * copy_remote_mm_str - copy a string from a remote address space.\n+ * @mm:         the remote address space\n+ * @addr:       start address to read from\n+ * @buf:        destination buffer\n+ * @len:        number of bytes to copy\n+ * @gup_flags:  flags modifying lookup behaviour (unused)\n+ *\n+ * The caller must hold a reference on @mm.\n+ *\n+ * Return: number of bytes copied from @addr (source) to @buf (destination),\n+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.\n+ * On any error, return -EFAULT.\n+ */\n+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n+\t\t       void *buf, int len, unsigned int gup_flags)\n+{\n+\tif (unlikely(len == 0))\n+\t\treturn 0;\n+\n+\treturn __copy_remote_vm_str(mm, addr, buf, len);\n+}\n+\n /**\n  * copy_remote_vm_str - copy a string from another process's address space.\n  * @tsk:\tthe task of the target address space\n@@ -1781,7 +1804,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n \t\treturn -EFAULT;\n \t}\n \n-\tret = __copy_remote_vm_str(mm, addr, buf, len);\n+\tret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);\n \n \tmmput(mm);\n \ndiff --git a/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c\nnew file mode 100644\nindex 0000000000000..3d5080a3975e3\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c\n@@ -0,0 +1,52 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003cerrno.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cunistd.h\u003e\n+\n+#include \u003ctest_progs.h\u003e\n+\n+#include \"copy_from_user_bprm.skel.h\"\n+\n+void test_copy_from_user_bprm(void)\n+{\n+\tstruct copy_from_user_bprm *skel;\n+\tpid_t child;\n+\tint status;\n+\n+\tskel = copy_from_user_bprm__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"open_and_load\"))\n+\t\treturn;\n+\n+\tif (!ASSERT_OK(copy_from_user_bprm__attach(skel), \"attach\"))\n+\t\tgoto out;\n+\n+\tchild = fork();\n+\tif (!ASSERT_GE(child, 0, \"fork\"))\n+\t\tgoto out;\n+\n+\tif (!child) {\n+\t\tchar *const argv[] = { \"first\", \"second-argument\", NULL };\n+\n+\t\tskel-\u003ebss-\u003emonitored_pid = getpid();\n+\t\texecv(\"/bin/true\", argv);\n+\t\t_exit(errno);\n+\t}\n+\n+\tif (!ASSERT_EQ(waitpid(child, \u0026status, 0), child, \"waitpid\"))\n+\t\tgoto out;\n+\n+\tif (ASSERT_TRUE(WIFEXITED(status), \"child_exited\"))\n+\t\tASSERT_EQ(WEXITSTATUS(status), EPERM, \"exec_errno\");\n+\n+\tASSERT_EQ(skel-\u003ebss-\u003ebprm_argc, 2, \"bprm_argc\");\n+\tASSERT_EQ(skel-\u003ebss-\u003einvalid_flags_ret, -EINVAL, \"invalid_flags_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003ecopy_ret, 0, \"copy_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_arg0_ret, 6, \"str_arg0_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_arg1_ret, 16, \"str_arg1_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eargs_match, 1, \"args_match\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_args_match, 1, \"str_args_match\");\n+\n+out:\n+\tcopy_from_user_bprm__destroy(skel);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c\nnew file mode 100644\nindex 0000000000000..d7132bca6edc2\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c\n@@ -0,0 +1,77 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \"vmlinux.h\"\n+\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \"bpf_misc.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+static const char expected_args[] = \"first\\0second-argument\";\n+static const char expected_arg0[] = \"first\";\n+static const char expected_arg1[] = \"second-argument\";\n+\n+int monitored_pid;\n+int bprm_argc;\n+int invalid_flags_ret;\n+int copy_ret;\n+int str_arg0_ret;\n+int str_arg1_ret;\n+int args_match;\n+int str_args_match;\n+\n+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\n+\t\t\t\tconst void *unsafe_ptr__ign,\n+\t\t\t\tstruct mm_struct *mm, u64 flags) __ksym;\n+\n+extern int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,\n+\t\t\t\t    const void *unsafe_ptr__ign,\n+\t\t\t\t    struct mm_struct *mm, u64 flags) __ksym;\n+\n+SEC(\"lsm.s/bprm_check_security\")\n+int BPF_PROG(check_exec_args, struct linux_binprm *bprm, int ret)\n+{\n+\tu32 pid = bpf_get_current_pid_tgid() \u003e\u003e 32;\n+\tstruct mm_struct *mm;\n+\tchar args[sizeof(expected_args)];\n+\tchar arg0[32];\n+\tchar arg1[32];\n+\n+\tif (ret || pid != monitored_pid)\n+\t\treturn ret;\n+\n+\tmm = bprm-\u003emm;\n+\tif (!mm)\n+\t\treturn 0;\n+\n+\tbprm_argc = bprm-\u003eargc;\n+\n+\tinvalid_flags_ret = bpf_copy_from_user_mm(args, sizeof(args),\n+\t\t\t\t\t\t  (void *)bprm-\u003ep, mm, 1);\n+\n+\tcopy_ret = bpf_copy_from_user_mm(args, sizeof(args),\n+\t\t\t\t\t (void *)bprm-\u003ep, mm, 0);\n+\tif (copy_ret)\n+\t\treturn 0;\n+\n+\targs_match = !__builtin_memcmp(args, expected_args, sizeof(expected_args));\n+\n+\tstr_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),\n+\t\t\t\t\t\t (void *)bprm-\u003ep, mm,\n+\t\t\t\t\t\t BPF_F_PAD_ZEROS);\n+\tif (str_arg0_ret != sizeof(expected_arg0))\n+\t\treturn 0;\n+\n+\tstr_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),\n+\t\t\t\t\t\t (void *)(bprm-\u003ep + str_arg0_ret),\n+\t\t\t\t\t\t mm, BPF_F_PAD_ZEROS);\n+\tif (str_arg1_ret != sizeof(expected_arg1))\n+\t\treturn 0;\n+\n+\tstr_args_match = !__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) \u0026\u0026\n+\t\t\t!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));\n+\n+\treturn args_match \u0026\u0026 str_args_match ? -EPERM : 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c\nindex 7de173daf27b6..7441d66c080c9 100644\n--- a/tools/testing/selftests/bpf/progs/lsm.c\n+++ b/tools/testing/selftests/bpf/progs/lsm.c\n@@ -113,6 +113,7 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)\n {\n \t__u32 pid = bpf_get_current_pid_tgid() \u003e\u003e 32;\n \tstruct inner_map *inner_map;\n+\tstruct mm_struct *mm;\n \tchar args[64];\n \t__u32 key = 0;\n \t__u64 *value;\n@@ -121,7 +122,9 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)\n \t\tbprm_count++;\n \n \tbpf_copy_from_user(args, sizeof(args), (void *)bprm-\u003evma-\u003evm_mm-\u003earg_start);\n-\tbpf_copy_from_user(args, sizeof(args), (void *)bprm-\u003emm-\u003earg_start);\n+\tmm = bprm-\u003emm;\n+\tif (mm)\n+\t\tbpf_copy_from_user(args, sizeof(args), (void *)mm-\u003earg_start);\n \n \tvalue = bpf_map_lookup_elem(\u0026array, \u0026key);\n \tif (value)\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c\nindex c724bf389f5c6..4fc835432af0a 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c\n@@ -5,6 +5,10 @@\n #include \u003cbpf/bpf_tracing.h\u003e\n #include \"bpf_misc.h\"\n \n+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\n+\t\t\t\tconst void *unsafe_ptr__ign,\n+\t\t\t\tstruct mm_struct *mm, u64 flags) __ksym;\n+\n SEC(\"lsm/file_permission\")\n __description(\"lsm bpf prog with -4095~0 retval. test 1\")\n __success\n@@ -212,4 +216,31 @@ __naked int retval_load_resets_bounds(void *ctx)\n \t::: __clobber_all);\n }\n \n+SEC(\"lsm.s/bprm_check_security\")\n+__description(\"null checking trusted-or-null linux_binprm mm\")\n+__success\n+int BPF_PROG(copy_from_user_mm_with_null_check, struct linux_binprm *bprm, int ret)\n+{\n+\tstruct mm_struct *mm;\n+\tchar dst[8];\n+\n+\tmm = bprm-\u003emm;\n+\tif (!mm)\n+\t\treturn 0;\n+\n+\tbpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm-\u003ep, mm, 0);\n+\treturn 0;\n+}\n+\n+SEC(\"lsm.s/bprm_check_security\")\n+__description(\"not null checking trusted-or-null linux_binprm mm\")\n+__failure __msg(\"Possibly NULL pointer passed to trusted R4\")\n+int BPF_PROG(copy_from_user_mm_without_null_check, struct linux_binprm *bprm, int ret)\n+{\n+\tchar dst[8];\n+\n+\tbpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm-\u003ep, bprm-\u003emm, 0);\n+\treturn 0;\n+}\n+\n char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/08/11 11:30 agent patch-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_EVENTS"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new BPF kfuncs `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` to allow BPF programs to copy data from a specific user address space (`struct mm_struct`). These functions are reachable from BPF programs and are worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


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

Prompt:
For your convenience, here is the diff of the changes:
commit 28654060c71ec6347349db4bcee1f3e5fec2ca06
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 11:30:38 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbddb..eede435bf4a3f 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3222,6 +3222,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
 		void *buf, int len, unsigned int gup_flags);
 
 #ifdef CONFIG_BPF_SYSCALL
+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			      void *buf, int len, unsigned int gup_flags);
 extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 			      void *buf, int len, unsigned int gup_flags);
 #endif
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 6388b6b23e490..9ba3362832cd0 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4931,6 +4931,8 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_local_irq_save)
 BTF_ID_FLAGS(func, bpf_local_irq_restore)
 #ifdef CONFIG_BPF_EVENTS
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
 BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
 BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index add3affc57035..8bdcb65618d01 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5682,6 +5682,10 @@ BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {
 	struct inode *d_inode;
 };
 
+BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm) {
+	struct mm_struct *mm;
+};
+
 BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket) {
 	struct sock *sk;
 };
@@ -5732,6 +5736,7 @@ static bool type_is_trusted_or_null(struct bpf_verifier_env *env,
 {
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));
+	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm));
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));
 
 	return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id,
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 891897f8a1b35..8931b3b2fc9d7 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -24,6 +24,7 @@
 #include <linux/key.h>
 #include <linux/namei.h>
 #include <linux/file.h>
+#include <linux/mm.h>
 
 #include <net/bpf_sk_storage.h>
 
@@ -3619,6 +3620,88 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid
 	return bpf_send_signal_common(sig, type, task, value);
 }
 
+/**
+ * bpf_copy_from_user_mm() - Copy data from an address space
+ * @dst:             Destination address, in kernel space
+ * @dst__sz:         Number of bytes to copy
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm:              Address space to copy from
+ * @flags:           Reserved for future use; must be zero
+ *
+ * Copies data from the user address space associated with @mm. The destination
+ * is zeroed if the requested number of bytes cannot be copied in full.
+ *
+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy
+ * fails or is partial.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				      const void __user *unsafe_ptr__ign,
+				      struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	if (unlikely(flags))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	ret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,
+			       dst, dst__sz, 0);
+	if (ret == dst__sz)
+		return 0;
+
+	memset(dst, 0, dst__sz);
+	return ret < 0 ? ret : -EFAULT;
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst:             Destination address, in kernel space. This buffer must be
+ *                   at least @dst__sz bytes long
+ * @dst__sz:         Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm:              Address space to copy from
+ * @flags:           The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If BPF_F_PAD_ZEROS is set, the unused portion of @dst is cleared on success
+ * and all of @dst is cleared on failure.
+ *
+ * Return: The number of copied bytes including the NUL terminator on success,
+ * or a negative error code on failure.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+					  const void __user *unsafe_ptr__ign,
+					  struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,
+				 dst, dst__sz, 0);
+	if (ret < 0)
+		goto err;
+
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst + ret, 0, dst__sz - ret);
+
+	return ret + 1;
+
+err:
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst, 0, dst__sz);
+	return ret;
+}
+
 __bpf_kfunc int bpf_probe_read_user_dynptr(const struct bpf_dynptr *dptr, u64 off,
 					   u64 size, const void __user *unsafe_ptr__ign)
 {
diff --git a/mm/memory.c b/mm/memory.c
index 6b8280cfc1dbe..03cbd3b02befb 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7218,6 +7218,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return buf - old_buf;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.
+ * On any error, return -EFAULT.
+ */
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	if (unlikely(len == 0))
+		return 0;
+
+	return __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+}
+
 /**
  * copy_remote_vm_str - copy a string from another process's address space.
  * @tsk:	the task of the target address space
@@ -7247,7 +7270,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
diff --git a/mm/nommu.c b/mm/nommu.c
index ed3934bc2de48..8fbe6306d0746 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1752,6 +1752,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return ret;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour (unused)
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.
+ * On any error, return -EFAULT.
+ */
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	if (unlikely(len == 0))
+		return 0;
+
+	return __copy_remote_vm_str(mm, addr, buf, len);
+}
+
 /**
  * copy_remote_vm_str - copy a string from another process's address space.
  * @tsk:	the task of the target address space
@@ -1781,7 +1804,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
diff --git a/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
new file mode 100644
index 0000000000000..3d5080a3975e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <test_progs.h>
+
+#include "copy_from_user_bprm.skel.h"
+
+void test_copy_from_user_bprm(void)
+{
+	struct copy_from_user_bprm *skel;
+	pid_t child;
+	int status;
+
+	skel = copy_from_user_bprm__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open_and_load"))
+		return;
+
+	if (!ASSERT_OK(copy_from_user_bprm__attach(skel), "attach"))
+		goto out;
+
+	child = fork();
+	if (!ASSERT_GE(child, 0, "fork"))
+		goto out;
+
+	if (!child) {
+		char *const argv[] = { "first", "second-argument", NULL };
+
+		skel->bss->monitored_pid = getpid();
+		execv("/bin/true", argv);
+		_exit(errno);
+	}
+
+	if (!ASSERT_EQ(waitpid(child, &status, 0), child, "waitpid"))
+		goto out;
+
+	if (ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+		ASSERT_EQ(WEXITSTATUS(status), EPERM, "exec_errno");
+
+	ASSERT_EQ(skel->bss->bprm_argc, 2, "bprm_argc");
+	ASSERT_EQ(skel->bss->invalid_flags_ret, -EINVAL, "invalid_flags_ret");
+	ASSERT_EQ(skel->bss->copy_ret, 0, "copy_ret");
+	ASSERT_EQ(skel->bss->str_arg0_ret, 6, "str_arg0_ret");
+	ASSERT_EQ(skel->bss->str_arg1_ret, 16, "str_arg1_ret");
+	ASSERT_EQ(skel->bss->args_match, 1, "args_match");
+	ASSERT_EQ(skel->bss->str_args_match, 1, "str_args_match");
+
+out:
+	copy_from_user_bprm__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
new file mode 100644
index 0000000000000..d7132bca6edc2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <errno.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+static const char expected_args[] = "first\0second-argument";
+static const char expected_arg0[] = "first";
+static const char expected_arg1[] = "second-argument";
+
+int monitored_pid;
+int bprm_argc;
+int invalid_flags_ret;
+int copy_ret;
+int str_arg0_ret;
+int str_arg1_ret;
+int args_match;
+int str_args_match;
+
+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				const void *unsafe_ptr__ign,
+				struct mm_struct *mm, u64 flags) __ksym;
+
+extern int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+				    const void *unsafe_ptr__ign,
+				    struct mm_struct *mm, u64 flags) __ksym;
+
+SEC("lsm.s/bprm_check_security")
+int BPF_PROG(check_exec_args, struct linux_binprm *bprm, int ret)
+{
+	u32 pid = bpf_get_current_pid_tgid() >> 32;
+	struct mm_struct *mm;
+	char args[sizeof(expected_args)];
+	char arg0[32];
+	char arg1[32];
+
+	if (ret || pid != monitored_pid)
+		return ret;
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bprm_argc = bprm->argc;
+
+	invalid_flags_ret = bpf_copy_from_user_mm(args, sizeof(args),
+						  (void *)bprm->p, mm, 1);
+
+	copy_ret = bpf_copy_from_user_mm(args, sizeof(args),
+					 (void *)bprm->p, mm, 0);
+	if (copy_ret)
+		return 0;
+
+	args_match = !__builtin_memcmp(args, expected_args, sizeof(expected_args));
+
+	str_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),
+						 (void *)bprm->p, mm,
+						 BPF_F_PAD_ZEROS);
+	if (str_arg0_ret != sizeof(expected_arg0))
+		return 0;
+
+	str_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),
+						 (void *)(bprm->p + str_arg0_ret),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg1_ret != sizeof(expected_arg1))
+		return 0;
+
+	str_args_match = !__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) &&
+			!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));
+
+	return args_match && str_args_match ? -EPERM : 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c
index 7de173daf27b6..7441d66c080c9 100644
--- a/tools/testing/selftests/bpf/progs/lsm.c
+++ b/tools/testing/selftests/bpf/progs/lsm.c
@@ -113,6 +113,7 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
 {
 	__u32 pid = bpf_get_current_pid_tgid() >> 32;
 	struct inner_map *inner_map;
+	struct mm_struct *mm;
 	char args[64];
 	__u32 key = 0;
 	__u64 *value;
@@ -121,7 +122,9 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
 		bprm_count++;
 
 	bpf_copy_from_user(args, sizeof(args), (void *)bprm->vma->vm_mm->arg_start);
-	bpf_copy_from_user(args, sizeof(args), (void *)bprm->mm->arg_start);
+	mm = bprm->mm;
+	if (mm)
+		bpf_copy_from_user(args, sizeof(args), (void *)mm->arg_start);
 
 	value = bpf_map_lookup_elem(&array, &key);
 	if (value)
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index c724bf389f5c6..4fc835432af0a 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -5,6 +5,10 @@
 #include <bpf/bpf_tracing.h>
 #include "bpf_misc.h"
 
+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				const void *unsafe_ptr__ign,
+				struct mm_struct *mm, u64 flags) __ksym;
+
 SEC("lsm/file_permission")
 __description("lsm bpf prog with -4095~0 retval. test 1")
 __success
@@ -212,4 +216,31 @@ __naked int retval_load_resets_bounds(void *ctx)
 	::: __clobber_all);
 }
 
+SEC("lsm.s/bprm_check_security")
+__description("null checking trusted-or-null linux_binprm mm")
+__success
+int BPF_PROG(copy_from_user_mm_with_null_check, struct linux_binprm *bprm, int ret)
+{
+	struct mm_struct *mm;
+	char dst[8];
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm->p, mm, 0);
+	return 0;
+}
+
+SEC("lsm.s/bprm_check_security")
+__description("not null checking trusted-or-null linux_binprm mm")
+__failure __msg("Possibly NULL pointer passed to trusted R4")
+int BPF_PROG(copy_from_user_mm_without_null_check, struct linux_binprm *bprm, int ret)
+{
+	char dst[8];
+
+	bpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm->p, bprm->mm, 0);
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";

3/2 2026/08/11 11:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7808
							output: 24
							thoughts: 427
						

Thoughts:
**Fuzzing Target: New BPF Copy-From-User Kfuncs**

Okay, so there's a patch introducing two new BPF kfuncs: `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`. These are designed to allow BPF programs to securely copy data from a *specific* user address space, represented by `struct mm_struct`. This is interesting, as it opens a new avenue for BPF to interact with user-space memory, and is something we should examine with the focus of fuzzing.

I see the patch touches a few key areas: `include/linux/mm.h`, the BPF helper implementation (`kernel/bpf/helpers.c`, `kernel/bpf/verifier.c`, `kernel/trace/bpf_trace.c`), and memory management code (`mm/memory.c`, `mm/nommu.c`), along with some selftests. Seems pretty comprehensive, which is good.

The new kfuncs are registered in `kernel/bpf/helpers.c`, but only under `#ifdef CONFIG_BPF_EVENTS`. That's an immediate flag – we'll need to make sure `CONFIG_BPF_EVENTS` is enabled and possibly `CONFIG_BPF_SYSCALL` to get this to work at all. It looks like `copy_remote_mm_str` is the workhorse here, defined in both `mm/memory.c` and `mm/nommu.c` and the actual call is based on the already existing `access_remote_vm`.

Now, the important question is: is this worth fuzzing? Absolutely. These are *new* kfuncs, which are specifically designed to be called from BPF programs. BPF programs are everywhere now - tracing, LSM, etc. This is a very interesting attack surface.

My focus should be on the following:
*   `bpf_copy_from_user_mm`
*   `bpf_copy_from_user_mm_str`
*   `copy_remote_mm_str`

Let's quickly check `CONFIG_BPF_EVENTS` and `CONFIG_BPF_SYSCALL` (and maybe others) to ensure the fuzzing build is appropriately configured and that the relevant KFuncs are available at runtime. Let's use `grepper` to be sure to get the exact definitions and configs.



4/2 2026/08/11 11:30 tool grepper
0m Args:
{
  "Expression": "bpf_copy_from_user_mm"
}

Results:
{
  "Output": "kernel/bpf/helpers.c=4932=BTF_ID_FLAGS(func, bpf_local_irq_restore)\nkernel/bpf/helpers.c-4933-#ifdef CONFIG_BPF_EVENTS\nkernel/bpf/helpers.c:4934:BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)\nkernel/bpf/helpers.c:4935:BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)\nkernel/bpf/helpers.c-4936-BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)\n--\nkernel/trace/bpf_trace.c=3614=__bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid_type type,\n--\nkernel/trace/bpf_trace.c-3623-/**\nkernel/trace/bpf_trace.c:3624: * bpf_copy_from_user_mm() - Copy data from an address space\nkernel/trace/bpf_trace.c-3625- * @dst:             Destination address, in kernel space\n--\nkernel/trace/bpf_trace.c-3636- */\nkernel/trace/bpf_trace.c:3637:__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\nkernel/trace/bpf_trace.c-3638-\t\t\t\t      const void __user *unsafe_ptr__ign,\n--\nkernel/trace/bpf_trace.c-3658-/**\nkernel/trace/bpf_trace.c:3659: * bpf_copy_from_user_mm_str() - Copy a string from an address space\nkernel/trace/bpf_trace.c-3660- * @dst:             Destination address, in kernel space. This buffer must be\n--\nkernel/trace/bpf_trace.c-3676- */\nkernel/trace/bpf_trace.c:3677:__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,\nkernel/trace/bpf_trace.c-3678-\t\t\t\t\t  const void __user *unsafe_ptr__ign,\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c=23=int str_args_match;\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-24-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:25:extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-26-\t\t\t\tconst void *unsafe_ptr__ign,\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-28-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:29:extern int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-30-\t\t\t\t    const void *unsafe_ptr__ign,\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c=34=int BPF_PROG(check_exec_args, struct linux_binprm *bprm, int ret)\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-50-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:51:\tinvalid_flags_ret = bpf_copy_from_user_mm(args, sizeof(args),\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-52-\t\t\t\t\t\t  (void *)bprm-\u003ep, mm, 1);\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-53-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:54:\tcopy_ret = bpf_copy_from_user_mm(args, sizeof(args),\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-55-\t\t\t\t\t (void *)bprm-\u003ep, mm, 0);\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-60-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:61:\tstr_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-62-\t\t\t\t\t\t (void *)bprm-\u003ep, mm,\n--\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-66-\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c:67:\tstr_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),\ntools/testing/selftests/bpf/progs/copy_from_user_bprm.c-68-\t\t\t\t\t\t (void *)(bprm-\u003ep + str_arg0_ret),\n--\ntools/testing/selftests/bpf/progs/verifier_lsm.c-7-\ntools/testing/selftests/bpf/progs/verifier_lsm.c:8:extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,\ntools/testing/selftests/bpf/progs/verifier_lsm.c-9-\t\t\t\tconst void *unsafe_ptr__ign,\n--\ntools/testing/selftests/bpf/progs/verifier_lsm.c=222=int BPF_PROG(copy_from_user_mm_with_null_check, struct linux_binprm *bprm, int ret)\n--\ntools/testing/selftests/bpf/progs/verifier_lsm.c-230-\ntools/testing/selftests/bpf/progs/verifier_lsm.c:231:\tbpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm-\u003ep, mm, 0);\ntools/testing/selftests/bpf/progs/verifier_lsm.c-232-\treturn 0;\n--\ntools/testing/selftests/bpf/progs/verifier_lsm.c=238=int BPF_PROG(copy_from_user_mm_without_null_check, struct linux_binprm *bprm, int ret)\n--\ntools/testing/selftests/bpf/progs/verifier_lsm.c-241-\ntools/testing/selftests/bpf/progs/verifier_lsm.c:242:\tbpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm-\u003ep, bprm-\u003emm, 0);\ntools/testing/selftests/bpf/progs/verifier_lsm.c-243-\treturn 0;\n"
}

5/2 2026/08/11 11:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10715
							output: 21
							thoughts: 76
						

Thoughts:
Okay, let's break this down.

**Analyzing Potential Memory Copy Functions in the BPF Context**

My initial focus is on these three BPF-related functions: `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, and `copy_remote_mm_str`. The naming conventions suggest they're all involved in copying data from user space memory into a BPF context, likely within the kernel's memory management system. I need to understand the specifics of how each one handles this process. The `mm` suffix suggests they're related to memory management structures, and the `str` variants hint at string handling, maybe with null termination concerns.

Now, my instinct is to dive into the source code to confirm my assumptions and get a deeper understanding of the actual mechanics. My first stop will be `mm/memory.c` and `mm/nommu.c`, as `copy_remote_mm_str` is likely implemented in one of them and it would act as a good jumping off point to assess each function from there.



6/2 2026/08/11 11:30 tool grepper
0m Args:
{
  "Expression": "copy_remote_mm_str"
}

Results:
{
  "Output": "include/linux/mm.h=3221=extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,\n--\ninclude/linux/mm.h-3224-#ifdef CONFIG_BPF_SYSCALL\ninclude/linux/mm.h:3225:extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\ninclude/linux/mm.h-3226-\t\t\t      void *buf, int len, unsigned int gup_flags);\n--\nkernel/trace/bpf_trace.c=3677=__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,\n--\nkernel/trace/bpf_trace.c-3688-\nkernel/trace/bpf_trace.c:3689:\tret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,\nkernel/trace/bpf_trace.c-3690-\t\t\t\t dst, dst__sz, 0);\n--\nmm/memory.c=7144=static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n--\nmm/memory.c-7221-/**\nmm/memory.c:7222: * copy_remote_mm_str - copy a string from a remote address space.\nmm/memory.c-7223- * @mm:         the remote address space\n--\nmm/memory.c-7234- */\nmm/memory.c:7235:int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/memory.c-7236-\t\t       void *buf, int len, unsigned int gup_flags)\n--\nmm/memory.c=7258=int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n--\nmm/memory.c-7272-\nmm/memory.c:7273:\tret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);\nmm/memory.c-7274-\n--\nmm/nommu.c=1719=static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n--\nmm/nommu.c-1755-/**\nmm/nommu.c:1756: * copy_remote_mm_str - copy a string from a remote address space.\nmm/nommu.c-1757- * @mm:         the remote address space\n--\nmm/nommu.c-1768- */\nmm/nommu.c:1769:int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/nommu.c-1770-\t\t       void *buf, int len, unsigned int gup_flags)\n--\nmm/nommu.c=1792=int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n--\nmm/nommu.c-1806-\nmm/nommu.c:1807:\tret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);\nmm/nommu.c-1808-\n"
}

7/2 2026/08/11 11:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11872
							output: 19
							thoughts: 85
						

Thoughts:
**Analyzing BPF Data Copying Mechanisms**

Okay, so I'm looking at these BPF-related functions: `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, and `copy_remote_mm_str`. My initial thought is to understand their roles within the broader context of user-to-kernel memory transfer within the BPF framework. These names strongly suggest they're dealing with secure data copying from user space to kernel space, a critical area for BPF's safety and functionality.

I'm also seeing that there are certain configuration options that need to be enabled for these functions. Specifically, `BPF_EVENTS` and `BPF_SYSCALL` are mentioned as necessary. This immediately makes me wonder about their interdependencies. `BPF_SYSCALL` seems obvious; it relates to using these functions in the context of syscall interception or tracing. But `BPF_EVENTS` is a little more nebulous.

My immediate next step is to drill down on whether `BPF_EVENTS` itself has any dependencies. Understanding these relationships is crucial. I need to make sure I'm fully aware of any prerequisites. This may reveal how these functions are integrated and what the minimal configuration needed to exploit their functionality is. I'm really looking to understand if `BPF_EVENTS` enables the use of these copy functions, or if it is just a higher level feature that they are used by.



8/2 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "config BPF_EVENTS"
}

Results:
{
  "Output": "kernel/trace/Kconfig=839=config EPROBE_EVENTS\n--\nkernel/trace/Kconfig-852-\nkernel/trace/Kconfig:853:config BPF_EVENTS\nkernel/trace/Kconfig-854-\tdepends on BPF_SYSCALL\n"
}

9/2 2026/08/11 11:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12369
							output: 21
							thoughts: 29
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the Dependency Landscape**

Alright, so I'm thinking about `BPF_EVENTS` and I'm immediately recognizing that it's inherently tied to the behavior of `BPF_SYSCALL`. We all know the dance, right? The interdependencies. It's second nature. Therefore, I need to get a clear picture of how `BPF_SYSCALL` is behaving in the current context, because that directly informs the behavior, capabilities and potential limitations of `BPF_EVENTS`. I need to start my diagnostic, analysis, or whatever I'm doing by understanding that foundational component. Right then, let's dive into `BPF_SYSCALL` and see what we're dealing with.



10/2 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "config BPF_SYSCALL"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n"
}

11/2 2026/08/11 11:31 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12681
							output: 152
							thoughts: 475
						

Thoughts:
**Analyzing BPF Copy Functions**

Okay, so I'm digging into these BPF copy functions. My initial focus is on the configurations related to this functionality, and it looks like `BPF_SYSCALL` and `BPF_EVENTS` are the key configurations in play.

Now, let's nail down the specific functions. I've got my sights set on:

*   `bpf_copy_from_user_mm`
*   `bpf_copy_from_user_mm_str`
*   `copy_remote_mm_str`

A quick check confirms that `copy_remote_mm_str` is indeed defined in both `mm/memory.c` and `mm/nommu.c`, and it's not declared static, which is helpful. Meanwhile, `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` are located within `kernel/trace/bpf_trace.c`. The patch seems focused and contained; no other functions seem relevant based on the current context.

Just to be absolutely sure, I'll double-check my configuration assumptions. Looking at `kernel/bpf/helpers.c`, the definitions of `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` are guarded by `#ifdef CONFIG_BPF_EVENTS`, confirming that this configuration is essential. Further, checking `include/linux/mm.h`, the declaration of `copy_remote_mm_str` is guarded by `#ifdef CONFIG_BPF_SYSCALL`, confirming this configuration is also necessary.

So, the configurations are definitely `BPF_SYSCALL` and `BPF_EVENTS`.

The targeted functions remain:

*   `bpf_copy_from_user_mm`
*   `bpf_copy_from_user_mm_str`
*   `copy_remote_mm_str`

Finally, let me just quickly confirm that `bpf_copy_from_user_mm` is actually *reachable* for BPF programs. And yes, it's a BPF kfunc, so it can be called from BPF programs.

Okay, that seems like a solid understanding of the landscape. Let's call `set-results`.



12/2 2026/08/11 11:31 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_EVENTS"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new BPF kfuncs `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` to allow BPF programs to copy data from a specific user address space (`struct mm_struct`). These functions are reachable from BPF programs and are worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "BPF_EVENTS"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new BPF kfuncs `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` to allow BPF programs to copy data from a specific user address space (`struct mm_struct`). These functions are reachable from BPF programs and are worth fuzzing.",
  "WorthFuzzing": true
}

13/1 2026/08/11 11:31 action If
3m Args:
{
  "WorthFuzzing": true
}

Results:
null

14/2 2026/08/11 11:31 agent kmsan-evaluator
3m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch introduces two new BPF kfuncs: `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`, which copy data from a user address space into a kernel buffer (`dst`). These kfuncs are available to privileged BPF programs (e.g., tracing or LSM programs). For privileged programs, the BPF verifier allows passing uninitialized kernel stack memory to kfuncs and assumes the kfunc will fully initialize the buffer (marking the entire buffer as initialized in the verifier state).\n\nHowever, `bpf_copy_from_user_mm_str` only fully initializes the `dst` buffer if the `BPF_F_PAD_ZEROS` flag is provided. If this flag is omitted, the function only initializes the buffer up to the length of the copied string (plus the NUL terminator), leaving the remainder of the buffer uninitialized. Because the verifier incorrectly marks the entire buffer as initialized, a BPF program can read the uninitialized tail of the buffer and leak kernel stack memory to userspace (e.g., via BPF maps).\n\nStandard KASAN cannot detect this issue because the memory accesses are strictly within the bounds of the allocated stack buffer. KMSAN is specifically designed to track uninitialized memory at runtime and will reliably detect this kernel info-leak when the BPF program reads the uninitialized bytes. Therefore, a dedicated KMSAN fuzzing session is highly justified.",
  "NeedsKMSAN": true
}

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

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

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

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

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

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

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


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

Prompt:
For your convenience, here is the diff of the changes:
commit 28654060c71ec6347349db4bcee1f3e5fec2ca06
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 11:30:38 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbddb..eede435bf4a3f 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3222,6 +3222,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
 		void *buf, int len, unsigned int gup_flags);
 
 #ifdef CONFIG_BPF_SYSCALL
+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			      void *buf, int len, unsigned int gup_flags);
 extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 			      void *buf, int len, unsigned int gup_flags);
 #endif
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 6388b6b23e490..9ba3362832cd0 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4931,6 +4931,8 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_local_irq_save)
 BTF_ID_FLAGS(func, bpf_local_irq_restore)
 #ifdef CONFIG_BPF_EVENTS
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
 BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
 BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index add3affc57035..8bdcb65618d01 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5682,6 +5682,10 @@ BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {
 	struct inode *d_inode;
 };
 
+BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm) {
+	struct mm_struct *mm;
+};
+
 BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket) {
 	struct sock *sk;
 };
@@ -5732,6 +5736,7 @@ static bool type_is_trusted_or_null(struct bpf_verifier_env *env,
 {
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));
+	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm));
 	BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));
 
 	return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id,
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 891897f8a1b35..8931b3b2fc9d7 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -24,6 +24,7 @@
 #include <linux/key.h>
 #include <linux/namei.h>
 #include <linux/file.h>
+#include <linux/mm.h>
 
 #include <net/bpf_sk_storage.h>
 
@@ -3619,6 +3620,88 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid
 	return bpf_send_signal_common(sig, type, task, value);
 }
 
+/**
+ * bpf_copy_from_user_mm() - Copy data from an address space
+ * @dst:             Destination address, in kernel space
+ * @dst__sz:         Number of bytes to copy
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm:              Address space to copy from
+ * @flags:           Reserved for future use; must be zero
+ *
+ * Copies data from the user address space associated with @mm. The destination
+ * is zeroed if the requested number of bytes cannot be copied in full.
+ *
+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy
+ * fails or is partial.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				      const void __user *unsafe_ptr__ign,
+				      struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	if (unlikely(flags))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	ret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,
+			       dst, dst__sz, 0);
+	if (ret == dst__sz)
+		return 0;
+
+	memset(dst, 0, dst__sz);
+	return ret < 0 ? ret : -EFAULT;
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst:             Destination address, in kernel space. This buffer must be
+ *                   at least @dst__sz bytes long
+ * @dst__sz:         Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm:              Address space to copy from
+ * @flags:           The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If BPF_F_PAD_ZEROS is set, the unused portion of @dst is cleared on success
+ * and all of @dst is cleared on failure.
+ *
+ * Return: The number of copied bytes including the NUL terminator on success,
+ * or a negative error code on failure.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+					  const void __user *unsafe_ptr__ign,
+					  struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,
+				 dst, dst__sz, 0);
+	if (ret < 0)
+		goto err;
+
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst + ret, 0, dst__sz - ret);
+
+	return ret + 1;
+
+err:
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst, 0, dst__sz);
+	return ret;
+}
+
 __bpf_kfunc int bpf_probe_read_user_dynptr(const struct bpf_dynptr *dptr, u64 off,
 					   u64 size, const void __user *unsafe_ptr__ign)
 {
diff --git a/mm/memory.c b/mm/memory.c
index 6b8280cfc1dbe..03cbd3b02befb 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7218,6 +7218,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return buf - old_buf;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.
+ * On any error, return -EFAULT.
+ */
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	if (unlikely(len == 0))
+		return 0;
+
+	return __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+}
+
 /**
  * copy_remote_vm_str - copy a string from another process's address space.
  * @tsk:	the task of the target address space
@@ -7247,7 +7270,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
diff --git a/mm/nommu.c b/mm/nommu.c
index ed3934bc2de48..8fbe6306d0746 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1752,6 +1752,29 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return ret;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour (unused)
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. Always guarantees a NUL-terminated buffer.
+ * On any error, return -EFAULT.
+ */
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	if (unlikely(len == 0))
+		return 0;
+
+	return __copy_remote_vm_str(mm, addr, buf, len);
+}
+
 /**
  * copy_remote_vm_str - copy a string from another process's address space.
  * @tsk:	the task of the target address space
@@ -1781,7 +1804,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
diff --git a/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
new file mode 100644
index 0000000000000..3d5080a3975e3
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <test_progs.h>
+
+#include "copy_from_user_bprm.skel.h"
+
+void test_copy_from_user_bprm(void)
+{
+	struct copy_from_user_bprm *skel;
+	pid_t child;
+	int status;
+
+	skel = copy_from_user_bprm__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open_and_load"))
+		return;
+
+	if (!ASSERT_OK(copy_from_user_bprm__attach(skel), "attach"))
+		goto out;
+
+	child = fork();
+	if (!ASSERT_GE(child, 0, "fork"))
+		goto out;
+
+	if (!child) {
+		char *const argv[] = { "first", "second-argument", NULL };
+
+		skel->bss->monitored_pid = getpid();
+		execv("/bin/true", argv);
+		_exit(errno);
+	}
+
+	if (!ASSERT_EQ(waitpid(child, &status, 0), child, "waitpid"))
+		goto out;
+
+	if (ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+		ASSERT_EQ(WEXITSTATUS(status), EPERM, "exec_errno");
+
+	ASSERT_EQ(skel->bss->bprm_argc, 2, "bprm_argc");
+	ASSERT_EQ(skel->bss->invalid_flags_ret, -EINVAL, "invalid_flags_ret");
+	ASSERT_EQ(skel->bss->copy_ret, 0, "copy_ret");
+	ASSERT_EQ(skel->bss->str_arg0_ret, 6, "str_arg0_ret");
+	ASSERT_EQ(skel->bss->str_arg1_ret, 16, "str_arg1_ret");
+	ASSERT_EQ(skel->bss->args_match, 1, "args_match");
+	ASSERT_EQ(skel->bss->str_args_match, 1, "str_args_match");
+
+out:
+	copy_from_user_bprm__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
new file mode 100644
index 0000000000000..d7132bca6edc2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <errno.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+static const char expected_args[] = "first\0second-argument";
+static const char expected_arg0[] = "first";
+static const char expected_arg1[] = "second-argument";
+
+int monitored_pid;
+int bprm_argc;
+int invalid_flags_ret;
+int copy_ret;
+int str_arg0_ret;
+int str_arg1_ret;
+int args_match;
+int str_args_match;
+
+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				const void *unsafe_ptr__ign,
+				struct mm_struct *mm, u64 flags) __ksym;
+
+extern int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+				    const void *unsafe_ptr__ign,
+				    struct mm_struct *mm, u64 flags) __ksym;
+
+SEC("lsm.s/bprm_check_security")
+int BPF_PROG(check_exec_args, struct linux_binprm *bprm, int ret)
+{
+	u32 pid = bpf_get_current_pid_tgid() >> 32;
+	struct mm_struct *mm;
+	char args[sizeof(expected_args)];
+	char arg0[32];
+	char arg1[32];
+
+	if (ret || pid != monitored_pid)
+		return ret;
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bprm_argc = bprm->argc;
+
+	invalid_flags_ret = bpf_copy_from_user_mm(args, sizeof(args),
+						  (void *)bprm->p, mm, 1);
+
+	copy_ret = bpf_copy_from_user_mm(args, sizeof(args),
+					 (void *)bprm->p, mm, 0);
+	if (copy_ret)
+		return 0;
+
+	args_match = !__builtin_memcmp(args, expected_args, sizeof(expected_args));
+
+	str_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),
+						 (void *)bprm->p, mm,
+						 BPF_F_PAD_ZEROS);
+	if (str_arg0_ret != sizeof(expected_arg0))
+		return 0;
+
+	str_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),
+						 (void *)(bprm->p + str_arg0_ret),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg1_ret != sizeof(expected_arg1))
+		return 0;
+
+	str_args_match = !__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) &&
+			!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));
+
+	return args_match && str_args_match ? -EPERM : 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c
index 7de173daf27b6..7441d66c080c9 100644
--- a/tools/testing/selftests/bpf/progs/lsm.c
+++ b/tools/testing/selftests/bpf/progs/lsm.c
@@ -113,6 +113,7 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
 {
 	__u32 pid = bpf_get_current_pid_tgid() >> 32;
 	struct inner_map *inner_map;
+	struct mm_struct *mm;
 	char args[64];
 	__u32 key = 0;
 	__u64 *value;
@@ -121,7 +122,9 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
 		bprm_count++;
 
 	bpf_copy_from_user(args, sizeof(args), (void *)bprm->vma->vm_mm->arg_start);
-	bpf_copy_from_user(args, sizeof(args), (void *)bprm->mm->arg_start);
+	mm = bprm->mm;
+	if (mm)
+		bpf_copy_from_user(args, sizeof(args), (void *)mm->arg_start);
 
 	value = bpf_map_lookup_elem(&array, &key);
 	if (value)
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index c724bf389f5c6..4fc835432af0a 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -5,6 +5,10 @@
 #include <bpf/bpf_tracing.h>
 #include "bpf_misc.h"
 
+extern int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+				const void *unsafe_ptr__ign,
+				struct mm_struct *mm, u64 flags) __ksym;
+
 SEC("lsm/file_permission")
 __description("lsm bpf prog with -4095~0 retval. test 1")
 __success
@@ -212,4 +216,31 @@ __naked int retval_load_resets_bounds(void *ctx)
 	::: __clobber_all);
 }
 
+SEC("lsm.s/bprm_check_security")
+__description("null checking trusted-or-null linux_binprm mm")
+__success
+int BPF_PROG(copy_from_user_mm_with_null_check, struct linux_binprm *bprm, int ret)
+{
+	struct mm_struct *mm;
+	char dst[8];
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm->p, mm, 0);
+	return 0;
+}
+
+SEC("lsm.s/bprm_check_security")
+__description("not null checking trusted-or-null linux_binprm mm")
+__failure __msg("Possibly NULL pointer passed to trusted R4")
+int BPF_PROG(copy_from_user_mm_without_null_check, struct linux_binprm *bprm, int ret)
+{
+	char dst[8];
+
+	bpf_copy_from_user_mm(dst, sizeof(dst), (void *)bprm->p, bprm->mm, 0);
+	return 0;
+}
+
 char _license[] SEC("license") = "GPL";

15/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 7805
							output: 28
							thoughts: 1143
						

Thoughts:
**Analyzing BPF kfuncs for User-to-Kernel Memory Copying and Initialization**

Okay, so I'm looking at this patch that introduces `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`. These are BPF kfuncs, which means they're kernel functions callable from BPF programs to copy data from a specific user address space (`mm_struct *mm`) into a kernel buffer (`dst`). The goal is to get user data securely into a place where BPF can read it. That's good. And of course, we need to consider KMSAN and uninitialized memory.

Right, the patch mentions that if the copy fails to get everything, the remaining part of the destination buffer `dst` is zeroed out. Let's look at `bpf_copy_from_user_mm` first. From the code, it looks like if the copy is successful (`ret == dst__sz`), then `dst` is fully initialized. If not, the entire `dst` is `memset` to zero. So far, so good. No potential issues there.

Now, `bpf_copy_from_user_mm_str` is a little more complex. It's supposed to handle strings. Okay, the code snippet checks if `BPF_F_PAD_ZEROS` is set. If it *is* set, then, on failure, it zeroes out the *whole* buffer, and on partial success, it pads the rest with zeros.

But here's the kicker: what if `BPF_F_PAD_ZEROS` is *not* set? The code *seems* to indicate that only the copied part of `dst` is initialized. Wait, the code checks: `unlikely(flags & ~BPF_F_PAD_ZEROS)`. Oh, right. This means that `flags` can only be `0` or `BPF_F_PAD_ZEROS`. So, if `flags` is zero, then we're in the situation where `memset` is *not* called. It looks like it could leave uninitialized memory, which could trigger a KMSAN issue.

Let's think through this more clearly. If the copy is *fully successful*, the function returns `ret + 1`, and the code *guarantees* that `dst` is initialized up to `ret` bytes (plus the terminating NULL), meaning it's fully initialized. However, if the copy is *partially successful*, then the buffer from `ret + 1` to `dst__sz` is uninitialized. This could be an issue.

Now, how does the BPF verifier handle this? It needs to track memory initialization. Does it *know* that these kfuncs initialize memory? They are just kfuncs. The verifier doesn't *automatically* know that `dst` is a buffer that gets initialized, unless it's annotated or handled specially.

`dst` is just a pointer! If it's on the stack, the BPF program *must* have initialized it beforehand, unless `dst` is an out parameter. BPF kfuncs usually don't support out parameters without annotations like `__uninit`. I should check the kfunc signature for any annotations.

Here's the signature: `__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz, const void __user *unsafe_ptr__ign, struct mm_struct *mm, u64 flags)`. No `__uninit` on `dst`.

So, in BPF, if an argument like `dst` isn't annotated with `__uninit`, the verifier *requires* the BPF program to initialize it before calling the kfunc. That's a key point. Now, let me verify this. I'll search for `__uninit` in the kernel's BPF code to confirm this verification requirement.



16/3 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "__uninit",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/helpers.c=3220=__bpf_kfunc int bpf_dynptr_clone(const struct bpf_dynptr *p,\nkernel/bpf/helpers.c:3221:\t\t\t\t struct bpf_dynptr *clone__uninit)\nkernel/bpf/helpers.c-3222-{\nkernel/bpf/helpers.c:3223:\tstruct bpf_dynptr_kern *clone = (struct bpf_dynptr_kern *)clone__uninit;\nkernel/bpf/helpers.c-3224-\tconst struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p;\n--\nkernel/bpf/helpers.c=4677=static int make_file_dynptr(struct file *file, u32 flags, bool may_sleep,\n--\nkernel/bpf/helpers.c-4700-\nkernel/bpf/helpers.c:4701:__bpf_kfunc int bpf_dynptr_from_file(struct file *file, u32 flags, struct bpf_dynptr *ptr__uninit)\nkernel/bpf/helpers.c-4702-{\nkernel/bpf/helpers.c:4703:\treturn make_file_dynptr(file, flags, false, (struct bpf_dynptr_kern *)ptr__uninit);\nkernel/bpf/helpers.c-4704-}\nkernel/bpf/helpers.c-4705-\nkernel/bpf/helpers.c:4706:int bpf_dynptr_from_file_sleepable(struct file *file, u32 flags, struct bpf_dynptr *ptr__uninit)\nkernel/bpf/helpers.c-4707-{\nkernel/bpf/helpers.c:4708:\treturn make_file_dynptr(file, flags, true, (struct bpf_dynptr_kern *)ptr__uninit);\nkernel/bpf/helpers.c-4709-}\n--\nkernel/bpf/verifier.c=10773=static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)\nkernel/bpf/verifier.c-10774-{\nkernel/bpf/verifier.c:10775:\treturn btf_param_match_suffix(btf, arg, \"__uninit\");\nkernel/bpf/verifier.c-10776-}\n"
}

17/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10449
							output: 37
							thoughts: 85
						

Thoughts:
Okay, here's my interpretation of these thoughts, formatted for you, as an expert in the field:

**Decoding This "__uninit" Puzzle**

Right, so the trace points to this `__uninit` suffix. My immediate thought is, what's it trying to tell us about the state of a variable? Specifically, I'm looking at `bpf_copy_from_user_mm`, and I'm catching on the argument is just `void *dst`, and it *isn't* `void *dst__uninit`. Interesting. This makes me consider what happens if the parameter doesn't have the `__uninit` suffix. Does the verifier *demand* that the BPF program actually goes and initializes that `dst` buffer first? Because that's what I'd expect. A declared pointer without the `__uninit` hint typically means the program is responsible for making sure the memory pointed to is valid and holds sensible data before use. Time to go dig through the code and figure out the exact usage of `is_kfunc_arg_uninit` and see how this all connects. Let's see how the verifier handles this.



18/3 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "is_kfunc_arg_uninit",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=10768=static bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)\n--\nkernel/bpf/verifier.c-10772-\nkernel/bpf/verifier.c:10773:static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)\nkernel/bpf/verifier.c-10774-{\n--\nkernel/bpf/verifier.c=12008=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12219-\nkernel/bpf/verifier.c:12220:\t\t\tif (is_kfunc_arg_uninit(btf, \u0026args[i]))\nkernel/bpf/verifier.c-12221-\t\t\t\tdynptr_arg_type |= MEM_UNINIT;\n--\nkernel/bpf/verifier.c=12708=s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-12762-\t\treturn -size;\nkernel/bpf/verifier.c:12763:\tif (is_kfunc_arg_uninit(btf, \u0026args[arg]))\nkernel/bpf/verifier.c-12764-\t\treturn -size;\n"
}

19/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11225
							output: 38
							thoughts: 57
						

Thoughts:
Here's my summary, as the thought process unfolds:

**Digging into Kernel BPF Verification**

Okay, hold on a second... That `bpf_kfunc_stack_access_bytes` function, the one the verifier uses, it seems to have a check for `is_kfunc_arg_uninit` baked right in there. Interesting! That could be key. Alright, time to dive into the source code to confirm and better understand. Let's get the file path ready: specifically, I need to open `kernel/bpf/verifier.c`. Let's see how this function is actually implemented.



20/3 2026/08/11 11:31 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 12708,
  "LineCount": 60
}

Results:
{
  "Contents": "12708:\ts64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n12709:\t\t\t\t\t int arg, int insn_idx)\n12710:\t{\n12711:\t\tstruct bpf_insn_aux_data *aux = \u0026env-\u003einsn_aux_data[insn_idx];\n12712:\t\tstruct bpf_call_arg_meta meta;\n12713:\t\tconst struct btf_param *args;\n12714:\t\tconst struct btf_type *t, *ref_t;\n12715:\t\tconst struct btf *btf;\n12716:\t\tu32 nargs, type_size;\n12717:\t\ts64 size;\n12718:\t\n12719:\t\tif (bpf_fetch_kfunc_arg_meta(env, insn-\u003eimm, insn-\u003eoff, \u0026meta) \u003c 0)\n12720:\t\t\treturn S64_MIN;\n12721:\t\n12722:\t\tbtf = meta.btf;\n12723:\t\targs = btf_params(meta.func_proto);\n12724:\t\tnargs = btf_type_vlen(meta.func_proto);\n12725:\t\tif (arg \u003e= nargs)\n12726:\t\t\treturn 0;\n12727:\t\n12728:\t\tt = btf_type_skip_modifiers(btf, args[arg].type, NULL);\n12729:\t\tif (!btf_type_is_ptr(t))\n12730:\t\t\treturn 0;\n12731:\t\n12732:\t\t/* dynptr: fixed 16-byte on-stack representation */\n12733:\t\tif (is_kfunc_arg_dynptr(btf, \u0026args[arg])) {\n12734:\t\t\tsize = BPF_DYNPTR_SIZE;\n12735:\t\t\tgoto out;\n12736:\t\t}\n12737:\t\n12738:\t\t/* ptr + __sz/__szk pair: size is in the next register */\n12739:\t\tif (arg + 1 \u003c nargs \u0026\u0026\n12740:\t\t    (btf_param_match_suffix(btf, \u0026args[arg + 1], \"__sz\") ||\n12741:\t\t     btf_param_match_suffix(btf, \u0026args[arg + 1], \"__szk\"))) {\n12742:\t\t\tint size_reg = BPF_REG_1 + arg + 1;\n12743:\t\n12744:\t\t\tif (aux-\u003econst_reg_mask \u0026 BIT(size_reg)) {\n12745:\t\t\t\tsize = (s64)aux-\u003econst_reg_vals[size_reg];\n12746:\t\t\t\tgoto out;\n12747:\t\t\t}\n12748:\t\t\treturn MAX_BPF_STACK;\n12749:\t\t}\n12750:\t\n12751:\t\t/* fixed-size pointed-to type: resolve via BTF */\n12752:\t\tref_t = btf_type_skip_modifiers(btf, t-\u003etype, NULL);\n12753:\t\tif (!IS_ERR(btf_resolve_size(btf, ref_t, \u0026type_size))) {\n12754:\t\t\tsize = type_size;\n12755:\t\t\tgoto out;\n12756:\t\t}\n12757:\t\n12758:\t\treturn S64_MIN;\n12759:\tout:\n12760:\t\t/* KF_ITER_NEW kfuncs initialize the iterator state at arg 0 */\n12761:\t\tif (arg == 0 \u0026\u0026 meta.kfunc_flags \u0026 KF_ITER_NEW)\n12762:\t\t\treturn -size;\n12763:\t\tif (is_kfunc_arg_uninit(btf, \u0026args[arg]))\n12764:\t\t\treturn -size;\n12765:\t\treturn size;\n12766:\t}\n12767:\t\n"
}

21/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12549
							output: 39
							thoughts: 309
						

Thoughts:
**Analyzing BPF Kfunc Argument Initialization**

Okay, so I've got this scenario with `bpf_kfunc_stack_access_bytes`. I've just realized something important: If the argument to this kfunc *isn't* initialized, the function returns a negative size. And if it returns a positive size, the verifier insists that the memory region be initialized before the kfunc is even called. Got it.

Now, let's look at `bpf_copy_from_user_mm`. I see it takes `void *dst` and `u32 dst__sz`. Right, `dst` is argument 0, and `dst__sz` is argument 1. Aha! `btf_param_match_suffix(btf, &args[arg + 1], "__sz")` *exactly* matches `dst__sz`! This is how the *size* of the copy is determined – it’s linked to `dst__sz`.

But wait… now I remember `is_kfunc_arg_uninit(btf, &args[arg])`. This is the check for whether something is "uninit" and looks for that `__uninit` suffix on the *dst* argument itself. Since `dst` doesn't have the `__uninit` suffix, `bpf_kfunc_stack_access_bytes` is going to return a *positive* size.

That's the key. A positive return means the verifier thinks `dst` is an *input* to the kfunc. The BPF program *must* initialize `dst` completely before it can even call this function! So, this means the BPF program must have already pre-initialized dst before calling the bpf kfunc.

Okay, let's now check how `check_kfunc_args` handles the `bpf_kfunc_stack_access_bytes` function specifically, since it's the gatekeeper.



22/3 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "bpf_kfunc_stack_access_bytes",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=12590=s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-12707- */\nkernel/bpf/verifier.c:12708:s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\nkernel/bpf/verifier.c-12709-\t\t\t\t int arg, int insn_idx)\n"
}

23/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13476
							output: 36
							thoughts: 21
						

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

**Investigating `bpf_kfunc_stack_access_bytes` Usage**

Okay, so I need to understand where `bpf_kfunc_stack_access_bytes` is being used. My first step is to trace all the call sites. I need to figure out the contexts in which this function is relevant, to get a handle on its purpose and its impact. This is where I start digging into the codebase. I need to build a mental model of how this kfunc fits into the larger picture of the BPF ecosystem. Let's get to work.



24/3 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "bpf_kfunc_stack_access_bytes",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/liveness.c=1397=static int record_arg_access(struct bpf_verifier_env *env,\n--\nkernel/bpf/liveness.c-1413-\t} else if (bpf_pseudo_kfunc_call(insn)) {\nkernel/bpf/liveness.c:1414:\t\tbytes = bpf_kfunc_stack_access_bytes(env, insn, arg_idx, insn_idx);\nkernel/bpf/liveness.c-1415-\t} else {\n--\nkernel/bpf/verifier.c=12590=s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-12707- */\nkernel/bpf/verifier.c:12708:s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\nkernel/bpf/verifier.c-12709-\t\t\t\t int arg, int insn_idx)\n"
}

25/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13963
							output: 38
							thoughts: 23
						

Thoughts:
Okay, let's dive into this. Here's my thought process laid out:

**Examining `record_arg_access` in `kernel/bpf/liveness.c`**

Alright, the next step is to examine the `record_arg_access` function. I need to refresh my memory and understand how it functions within the larger BPF liveness analysis framework. Since I'm working at this level of the kernel, I'm already picturing the surrounding code. I'm focusing on `kernel/bpf/liveness.c` because that filename, and the function name, strongly suggests it's related to tracking argument usage, perhaps for optimization or error detection within BPF programs. I'm going to look at the implementation details, the inputs it receives, how it's called, and what data structures it manipulates. My objective is to build a complete picture of its purpose and impact. I'm specifically looking for insights into how argument liveness is determined and tracked, because that's what `record_arg_access` implies.



26/3 2026/08/11 11:31 tool read-file
0m Args:
{
  "File": "kernel/bpf/liveness.c",
  "FirstLine": 1397,
  "LineCount": 40
}

Results:
{
  "Contents": "1397:\tstatic int record_arg_access(struct bpf_verifier_env *env,\n1398:\t\t\t\t     struct func_instance *instance,\n1399:\t\t\t\t     struct bpf_insn *insn,\n1400:\t\t\t\t     struct arg_track *at, int arg_idx,\n1401:\t\t\t\t     int insn_idx)\n1402:\t{\n1403:\t\tint depth = instance-\u003edepth;\n1404:\t\tint frame = at-\u003eframe;\n1405:\t\tint err = 0;\n1406:\t\ts64 bytes;\n1407:\t\n1408:\t\tif (!arg_is_fp(at))\n1409:\t\t\treturn 0;\n1410:\t\n1411:\t\tif (bpf_helper_call(insn)) {\n1412:\t\t\tbytes = bpf_helper_stack_access_bytes(env, insn, arg_idx, insn_idx);\n1413:\t\t} else if (bpf_pseudo_kfunc_call(insn)) {\n1414:\t\t\tbytes = bpf_kfunc_stack_access_bytes(env, insn, arg_idx, insn_idx);\n1415:\t\t} else {\n1416:\t\t\tfor (int f = 0; f \u003c= depth; f++) {\n1417:\t\t\t\terr = mark_stack_read(instance, f, insn_idx, SPIS_ALL);\n1418:\t\t\t\tif (err)\n1419:\t\t\t\t\treturn err;\n1420:\t\t\t}\n1421:\t\t\treturn 0;\n1422:\t\t}\n1423:\t\tif (bytes == 0)\n1424:\t\t\treturn 0;\n1425:\t\n1426:\t\tif (frame \u003e= 0 \u0026\u0026 frame \u003c= depth)\n1427:\t\t\terr = record_stack_access(instance, at, bytes, frame, insn_idx);\n1428:\t\telse if (frame == ARG_IMPRECISE)\n1429:\t\t\terr = record_imprecise(instance, at-\u003emask, insn_idx);\n1430:\t\treturn err;\n1431:\t}\n1432:\t\n1433:\t/* Record stack access for a given 'at' state of helper/kfunc 'insn' */\n1434:\tstatic int record_call_access(struct bpf_verifier_env *env,\n1435:\t\t\t\t      struct func_instance *instance,\n1436:\t\t\t\t      struct arg_track *at,\n"
}

27/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14911
							output: 38
							thoughts: 44
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Connecting the Dots in the BPF Backend**

Hmm, hold on a second. I was just thinking about how we handle argument checking, and now I'm connecting the dots a little differently. I was focused on how we validate the arguments in the verifier. That's usually where my mind goes, especially with complex Kfuncs. But now I'm remembering that `record_arg_access` – that's *specifically* in `liveness.c`. That function's primary purpose is, of course, to keep track of liveness information. And that's critical for correctly tracking argument usage.

So, I need to revisit that. The implication is, while I'm using `check_kfunc_args` in `verifier.c` to do the *validation* and enforce the rules, it looks like `record_arg_access` in `liveness.c` is actually handling the lower-level tracking of whether or how those arguments are being *used* during the program's execution, which is foundational to the verification process. I need to make sure I understand the interaction between those two components better, because it looks like a subtle dependency might be emerging, especially for those more complex functions.



28/3 2026/08/11 11:31 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 12180,
  "LineCount": 60
}

Results:
{
  "Contents": "12180:\t\t\t\t}\n12181:\t\t\t\tbreak;\n12182:\t\t\tcase KF_ARG_PTR_TO_ARENA:\n12183:\t\t\t\tif (reg-\u003etype != PTR_TO_ARENA \u0026\u0026 reg-\u003etype != SCALAR_VALUE) {\n12184:\t\t\t\t\tverbose(env, \"%s is not a pointer to arena or scalar\\n\",\n12185:\t\t\t\t\t\treg_arg_name(env, argno));\n12186:\t\t\t\t\treturn -EINVAL;\n12187:\t\t\t\t}\n12188:\t\t\t\tbreak;\n12189:\t\t\tcase KF_ARG_PTR_TO_ALLOC_BTF_ID:\n12190:\t\t\t\tif (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC)) {\n12191:\t\t\t\t\tif (!is_bpf_obj_drop_kfunc(meta-\u003efunc_id)) {\n12192:\t\t\t\t\t\tverbose(env, \"%s expected for bpf_obj_drop()\\n\",\n12193:\t\t\t\t\t\t\treg_arg_name(env, argno));\n12194:\t\t\t\t\t\treturn -EINVAL;\n12195:\t\t\t\t\t}\n12196:\t\t\t\t} else if (reg-\u003etype == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) {\n12197:\t\t\t\t\tif (!is_bpf_percpu_obj_drop_kfunc(meta-\u003efunc_id)) {\n12198:\t\t\t\t\t\tverbose(env, \"%s expected for bpf_percpu_obj_drop()\\n\",\n12199:\t\t\t\t\t\t\treg_arg_name(env, argno));\n12200:\t\t\t\t\t\treturn -EINVAL;\n12201:\t\t\t\t\t}\n12202:\t\t\t\t} else {\n12203:\t\t\t\t\tverbose(env, \"%s expected pointer to allocated object\\n\",\n12204:\t\t\t\t\t\treg_arg_name(env, argno));\n12205:\t\t\t\t\treturn -EINVAL;\n12206:\t\t\t\t}\n12207:\t\t\t\tif (!reg_is_referenced(env, reg)) {\n12208:\t\t\t\t\tverbose(env, \"allocated object must be referenced\\n\");\n12209:\t\t\t\t\treturn -EINVAL;\n12210:\t\t\t\t}\n12211:\t\t\t\tif (meta-\u003ebtf == btf_vmlinux) {\n12212:\t\t\t\t\tmeta-\u003earg_btf = reg-\u003ebtf;\n12213:\t\t\t\t\tmeta-\u003earg_btf_id = reg-\u003ebtf_id;\n12214:\t\t\t\t}\n12215:\t\t\t\tbreak;\n12216:\t\t\tcase KF_ARG_PTR_TO_DYNPTR:\n12217:\t\t\t{\n12218:\t\t\t\tenum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR;\n12219:\t\n12220:\t\t\t\tif (is_kfunc_arg_uninit(btf, \u0026args[i]))\n12221:\t\t\t\t\tdynptr_arg_type |= MEM_UNINIT;\n12222:\t\n12223:\t\t\t\tif (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) {\n12224:\t\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_SKB;\n12225:\t\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) {\n12226:\t\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_XDP;\n12227:\t\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) {\n12228:\t\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_SKB_META;\n12229:\t\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_from_file]) {\n12230:\t\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_FILE;\n12231:\t\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) {\n12232:\t\t\t\t\tdynptr_arg_type |= DYNPTR_TYPE_FILE | OBJ_RELEASE;\n12233:\t\t\t\t} else if (meta-\u003efunc_id == special_kfunc_list[KF_bpf_dynptr_clone] \u0026\u0026\n12234:\t\t\t\t\t   (dynptr_arg_type \u0026 MEM_UNINIT)) {\n12235:\t\t\t\t\tenum bpf_dynptr_type parent_type = meta-\u003edynptr.type;\n12236:\t\n12237:\t\t\t\t\tif (parent_type == BPF_DYNPTR_TYPE_INVALID) {\n12238:\t\t\t\t\t\tverifier_bug(env, \"no dynptr type for parent of clone\");\n12239:\t\t\t\t\t\treturn -EFAULT;\n"
}

29/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16534
							output: 37
							thoughts: 46
						

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

**Navigating Kernel Function Argument Handling**

Okay, so I'm thinking about how kernel functions handle their arguments, specifically those related to memory pointers. And I seem to recall that the `check_kfunc_args` function is the one that's responsible for the `KF_ARG_PTR_TO_MEM` argument type. Therefore, to understand the argument handling for pointers to memory, I should really dive into the logic within `check_kfunc_args`. That's where the relevant checks and validations will be. Let's get to that file and pinpoint the relevant code section.



30/3 2026/08/11 11:31 tool grepper
0m Args:
{
  "Expression": "KF_ARG_PTR_TO_MEM",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=11005=enum kfunc_ptr_arg_type {\n--\nkernel/bpf/verifier.c-11018-\tKF_ARG_PTR_TO_BTF_ID,\t       /* Also covers reg2btf_ids conversions */\nkernel/bpf/verifier.c:11019:\tKF_ARG_PTR_TO_MEM,\nkernel/bpf/verifier.c-11020-\tKF_ARG_PTR_TO_CALLBACK,\n--\nkernel/bpf/verifier.c=11287=get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-11395-\t\t}\nkernel/bpf/verifier.c:11396:\t\targ_type = KF_ARG_PTR_TO_MEM;\nkernel/bpf/verifier.c-11397-\t} else if (btf_type_is_struct(ref_t))\n--\nkernel/bpf/verifier.c-11411-\t\t}\nkernel/bpf/verifier.c:11412:\t\targ_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\nkernel/bpf/verifier.c-11413-\t}\n--\nkernel/bpf/verifier.c=12008=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12106-\t\tcase KF_ARG_PTR_TO_RB_NODE:\nkernel/bpf/verifier.c:12107:\t\tcase KF_ARG_PTR_TO_MEM:\nkernel/bpf/verifier.c-12108-\t\tcase KF_ARG_PTR_TO_CALLBACK:\n--\nkernel/bpf/verifier.c-12393-\t\t\t */\nkernel/bpf/verifier.c:12394:\t\t\tkf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\nkernel/bpf/verifier.c-12395-\t\t\tfallthrough;\nkernel/bpf/verifier.c:12396:\t\tcase KF_ARG_PTR_TO_MEM:\nkernel/bpf/verifier.c-12397-\t\t\tif (kf_arg_type \u0026 MEM_FIXED_SIZE) {\n"
}

31/3 2026/08/11 11:31 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17380
							output: 38
							thoughts: 21
						

Thoughts:
Okay, let's dive into this code.

**Analyzing `check_kfunc_args`**

Alright, I'm going to zero in on that `check_kfunc_args` function, specifically around line 12396. I want to understand how it's handling argument validation within this kernel function context. Given my experience, I'm expecting to see checks for type compatibility, pointer validity (if applicable), potential size limitations based on what I know about the target architecture, and maybe even some basic sanity checks on values that could lead to vulnerabilities. I'm also anticipating that it likely integrates with some sort of metadata describing the expected arguments of the kernel function. I'll be looking to see if it's handling potential overflows and how it manages error reporting, especially if it's returning specific error codes or throwing exceptions. Let's see what the implementation details reveal about how this critical check is implemented. I'm keen to see if this is robust and secure, or whether there are potential vulnerabilities to look for.



32/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 12390,
  "LineCount": 60
}

Results:
{
  "Contents": "12390:\t\t\t\t/*\n12391:\t\t\t\t * If the register does not contain btf id but the argument type is a pointer to\n12392:\t\t\t\t * scalar-only struct, allow verifying it as a fixed size memory.\n12393:\t\t\t\t */\n12394:\t\t\t\tkf_arg_type = KF_ARG_PTR_TO_MEM | MEM_FIXED_SIZE;\n12395:\t\t\t\tfallthrough;\n12396:\t\t\tcase KF_ARG_PTR_TO_MEM:\n12397:\t\t\t\tif (kf_arg_type \u0026 MEM_FIXED_SIZE) {\n12398:\t\t\t\t\tresolve_ret = btf_resolve_size(btf, ref_t, \u0026type_size);\n12399:\t\t\t\t\tif (IS_ERR(resolve_ret)) {\n12400:\t\t\t\t\t\tverbose(env, \"%s reference type('%s %s') size cannot be determined: %ld\\n\",\n12401:\t\t\t\t\t\t\treg_arg_name(env, argno), btf_type_str(ref_t),\n12402:\t\t\t\t\t\t\tref_tname, PTR_ERR(resolve_ret));\n12403:\t\t\t\t\t\treturn -EINVAL;\n12404:\t\t\t\t\t}\n12405:\t\t\t\t\tret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE, meta);\n12406:\t\t\t\t\tif (ret \u003c 0)\n12407:\t\t\t\t\t\treturn ret;\n12408:\t\t\t\t}\n12409:\t\t\t\tbreak;\n12410:\t\t\tcase KF_ARG_CONST_MEM_SIZE:\n12411:\t\t\t\tret = process_const_arg(env, reg, argno, meta);\n12412:\t\t\t\tif (ret \u003c 0)\n12413:\t\t\t\t\treturn ret;\n12414:\t\t\t\tfallthrough;\n12415:\t\t\tcase KF_ARG_MEM_SIZE:\n12416:\t\t\t{\n12417:\t\t\t\tstruct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);\n12418:\t\t\t\tstruct bpf_reg_state *size_reg = reg;\n12419:\t\t\t\targno_t buff_argno = argno_from_arg(i);\n12420:\t\n12421:\t\t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n12422:\t\t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n12423:\t\t\t\t\treturn -EINVAL;\n12424:\t\t\t\t}\n12425:\t\n12426:\t\t\t\tif (bpf_register_is_null(buff_reg))\n12427:\t\t\t\t\tbreak;\n12428:\t\n12429:\t\t\t\tret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,\n12430:\t\t\t\t\t\t\t BPF_READ | BPF_WRITE, true, meta);\n12431:\t\t\t\tif (ret \u003c 0) {\n12432:\t\t\t\t\tverbose(env, \"%s and \", reg_arg_name(env, buff_argno));\n12433:\t\t\t\t\tverbose(env, \"%s memory, len pair leads to invalid memory access\\n\",\n12434:\t\t\t\t\t\treg_arg_name(env, argno));\n12435:\t\t\t\t\treturn ret;\n12436:\t\t\t\t}\n12437:\t\t\t\tbreak;\n12438:\t\t\t}\n12439:\t\t\tcase KF_ARG_PTR_TO_CALLBACK:\n12440:\t\t\t\tif (reg-\u003etype != PTR_TO_FUNC) {\n12441:\t\t\t\t\tverbose(env, \"%s expected pointer to func\\n\", reg_arg_name(env, argno));\n12442:\t\t\t\t\treturn -EINVAL;\n12443:\t\t\t\t}\n12444:\t\t\t\tmeta-\u003esubprogno = reg-\u003esubprogno;\n12445:\t\t\t\tbreak;\n12446:\t\t\tcase KF_ARG_PTR_TO_REFCOUNTED_KPTR:\n12447:\t\t\t\tif (!type_is_ptr_alloc_obj(reg-\u003etype)) {\n12448:\t\t\t\t\tverbose(env, \"%s is neither owning or non-owning ref\\n\",\n12449:\t\t\t\t\t\treg_arg_name(env, argno));\n"
}

33/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18787
							output: 35
							thoughts: 39
						

Thoughts:
Okay, let's see...

**Analyzing `check_mem_size_reg`'s Calls**

Hmm, so `check_mem_size_reg` is being invoked with `BPF_READ | BPF_WRITE`. That's a little unusual, I need to understand that. I need to take a closer look at what that function is doing with both read and write access. Let me dive into the implementation of `check_mem_size_reg` immediately. I need to see what's happening.



34/3 2026/08/11 11:32 tool grepper
0m Args:
{
  "Expression": "check_mem_size_reg",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=6739=static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-6828- */\nkernel/bpf/verifier.c:6829:static int check_mem_size_reg(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-6830-\t\t\t      struct bpf_reg_state *mem_reg,\n--\nkernel/bpf/verifier.c=8277=static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n--\nkernel/bpf/verifier.c-8457-\tcase ARG_MEM_SIZE:\nkernel/bpf/verifier.c:8458:\t\terr = check_mem_size_reg(env, reg_state(env, regno - 1), reg,\nkernel/bpf/verifier.c-8459-\t\t\t\t\t argno_from_reg(regno - 1), argno,\n--\nkernel/bpf/verifier.c-8463-\tcase ARG_MEM_SIZE_OR_ZERO:\nkernel/bpf/verifier.c:8464:\t\terr = check_mem_size_reg(env, reg_state(env, regno - 1), reg,\nkernel/bpf/verifier.c-8465-\t\t\t\t\t argno_from_reg(regno - 1), argno,\n--\nkernel/bpf/verifier.c=12008=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12428-\nkernel/bpf/verifier.c:12429:\t\t\tret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,\nkernel/bpf/verifier.c-12430-\t\t\t\t\t\t BPF_READ | BPF_WRITE, true, meta);\n"
}

35/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19502
							output: 37
							thoughts: 13
						

Thoughts:
Okay, let's dive into this code.

**Analyzing `check_mem_size_reg`**

Alright, I'm going to start by taking a look at `check_mem_size_reg`. This function name immediately suggests it's related to validating or checking memory sizes, likely against some register values. My initial thought is it's probably part of a larger memory management or allocation system. I need to understand how it's being used, what registers are involved, and what the overall goal of this check is. Is it for security, resource optimization, error handling, or something else entirely? Time to dig in and figure out the exact context. I'm expecting some register access, potentially memory comparisons, and possibly error-handling routines if size limits are violated. Let's see what we've got.



36/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6829,
  "LineCount": 40
}

Results:
{
  "Contents": "6829:\tstatic int check_mem_size_reg(struct bpf_verifier_env *env,\n6830:\t\t\t\t      struct bpf_reg_state *mem_reg,\n6831:\t\t\t\t      struct bpf_reg_state *size_reg, argno_t mem_argno,\n6832:\t\t\t\t      argno_t size_argno, u32 access_type,\n6833:\t\t\t\t      bool zero_size_allowed,\n6834:\t\t\t\t      struct bpf_call_arg_meta *meta)\n6835:\t{\n6836:\t\tint err = 0;\n6837:\t\n6838:\t\t/* This is used to refine r0 return value bounds for helpers\n6839:\t\t * that enforce this value as an upper bound on return values.\n6840:\t\t * See do_refine_retval_range() for helpers that can refine\n6841:\t\t * the return value. C type of helper is u32 so we pull register\n6842:\t\t * bound from umax_value however, if negative verifier errors\n6843:\t\t * out. Only upper bounds can be learned because retval is an\n6844:\t\t * int type and negative retvals are allowed.\n6845:\t\t */\n6846:\t\tmeta-\u003emsize_max_value = reg_umax(size_reg);\n6847:\t\n6848:\t\t/* The register is SCALAR_VALUE; the access check happens using\n6849:\t\t * its boundaries. For unprivileged variable accesses, disable\n6850:\t\t * raw mode so that the program is required to initialize all\n6851:\t\t * the memory that the helper could just partially fill up.\n6852:\t\t */\n6853:\t\tif (!tnum_is_const(size_reg-\u003evar_off))\n6854:\t\t\tmeta = NULL;\n6855:\t\n6856:\t\tif (reg_smin(size_reg) \u003c 0) {\n6857:\t\t\tverbose(env, \"%s min value is negative, either use unsigned or 'var \u0026= const'\\n\",\n6858:\t\t\t\treg_arg_name(env, size_argno));\n6859:\t\t\treturn -EACCES;\n6860:\t\t}\n6861:\t\n6862:\t\tif (reg_umin(size_reg) == 0 \u0026\u0026 !zero_size_allowed) {\n6863:\t\t\tverbose(env, \"%s invalid zero-sized read: u64=[%lld,%lld]\\n\",\n6864:\t\t\t\treg_arg_name(env, size_argno), reg_umin(size_reg), reg_umax(size_reg));\n6865:\t\t\treturn -EACCES;\n6866:\t\t}\n6867:\t\n6868:\t\tif (reg_umax(size_reg) \u003e= BPF_MAX_VAR_SIZ) {\n"
}

37/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20467
							output: 37
							thoughts: 50
						

Thoughts:
Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with memory management and register access, presented as a first-person account:

**Analyzing the `check_mem_size_reg` Function**

Hmm, hold on a second. I was under the impression that `check_mem_size_reg` used `check_helper_mem_access` as a subroutine for some memory validation. But, reviewing the code, it looks like that's not the case... at least not directly in *this* part of the execution path. Right, time to dig deeper. I need to refresh my understanding of the complete logic within `check_mem_size_reg`. Let's systematically read through the rest of that function to understand all its interactions and dependencies, especially to see if `check_helper_mem_access` is called conditionally or indirectly through another function it might be calling. Gotta make sure I haven't missed a subtly nested call.



38/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6869,
  "LineCount": 40
}

Results:
{
  "Contents": "6869:\t\t\tverbose(env, \"%s unbounded memory access, use 'var \u0026= const' or 'if (var \u003c const)'\\n\",\n6870:\t\t\t\treg_arg_name(env, size_argno));\n6871:\t\t\treturn -EACCES;\n6872:\t\t}\n6873:\t\n6874:\t\tif (access_type \u0026 BPF_READ)\n6875:\t\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n6876:\t\t\t\t\t\t      BPF_READ, zero_size_allowed, meta);\n6877:\t\tif (!err \u0026\u0026 access_type \u0026 BPF_WRITE)\n6878:\t\t\terr = check_helper_mem_access(env, mem_reg, mem_argno, reg_umax(size_reg),\n6879:\t\t\t\t\t\t      BPF_WRITE, zero_size_allowed, meta);\n6880:\t\n6881:\t\tif (!err) {\n6882:\t\t\tint regno = reg_from_argno(size_argno);\n6883:\t\n6884:\t\t\tif (regno \u003e= 0)\n6885:\t\t\t\terr = mark_chain_precision(env, regno);\n6886:\t\t\telse\n6887:\t\t\t\terr = mark_stack_arg_precision(env, arg_idx_from_argno(size_argno));\n6888:\t\t}\n6889:\t\n6890:\t\treturn err;\n6891:\t}\n6892:\t\n6893:\tstatic int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n6894:\t\t\t\t argno_t argno, u32 mem_size, enum bpf_access_type access_type,\n6895:\t\t\t\t struct bpf_call_arg_meta *meta)\n6896:\t{\n6897:\t\tint size, err = 0;\n6898:\t\n6899:\t\tif (bpf_register_is_null(reg))\n6900:\t\t\treturn 0;\n6901:\t\n6902:\t\tif (mem_size \u003e S32_MAX) {\n6903:\t\t\tverbose(env, \"%s memory size %u is too large\\n\",\n6904:\t\t\t\treg_arg_name(env, argno), mem_size);\n6905:\t\t\treturn -EACCES;\n6906:\t\t}\n6907:\t\n6908:\t\t/*\n"
}

39/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21426
							output: 37
							thoughts: 255
						

Thoughts:
**Analyzing Memory Access in BPF Helper Functions**

Okay, so I see that `check_mem_size_reg` calls `check_helper_mem_access`, passing `BPF_READ` and `BPF_WRITE`. Interesting. But wait, in `check_kfunc_args`, it also passes `BPF_READ | BPF_WRITE` to `check_mem_size_reg`. This needs a closer look.

Let's quickly refresh on what those flags signify within `check_helper_mem_access`. My understanding is that `BPF_READ` indicates the helper *reads* data from the memory, thus the memory *must* be initialized beforehand. Conversely, `BPF_WRITE` means the helper *writes* to the memory. It's pretty straightforward.

However, the combination, `BPF_READ | BPF_WRITE`, is where it gets interesting. This implies the helper *both* reads and writes. If it's reading, the BPF program *must* initialize the memory before calling this kfunc. This is crucial for data integrity and predictable behavior. Therefore, time to deep-dive into the `check_helper_mem_access` function. This is likely the key to understanding the memory access validation here.



40/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6739,
  "LineCount": 60
}

Results:
{
  "Contents": "6739:\tstatic int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n6740:\t\t\t\t\t   int access_size, enum bpf_access_type access_type,\n6741:\t\t\t\t\t   bool zero_size_allowed,\n6742:\t\t\t\t\t   struct bpf_call_arg_meta *meta)\n6743:\t{\n6744:\t\tstruct bpf_reg_state *regs = cur_regs(env);\n6745:\t\tu32 *max_access;\n6746:\t\n6747:\t\tswitch (base_type(reg-\u003etype)) {\n6748:\t\tcase PTR_TO_PACKET:\n6749:\t\tcase PTR_TO_PACKET_META:\n6750:\t\t\treturn check_packet_access(env, reg, argno, 0, access_size,\n6751:\t\t\t\t\t\t   zero_size_allowed);\n6752:\t\tcase PTR_TO_MAP_KEY:\n6753:\t\t\tif (access_type == BPF_WRITE) {\n6754:\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n6755:\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n6756:\t\t\t\treturn -EACCES;\n6757:\t\t\t}\n6758:\t\t\treturn check_mem_region_access(env, reg, argno, 0, access_size,\n6759:\t\t\t\t\t\t       reg-\u003emap_ptr-\u003ekey_size, false);\n6760:\t\tcase PTR_TO_MAP_VALUE:\n6761:\t\t\tif (check_map_access_type(env, reg, 0, access_size, access_type))\n6762:\t\t\t\treturn -EACCES;\n6763:\t\t\treturn check_map_access(env, reg, argno, 0, access_size,\n6764:\t\t\t\t\t\tzero_size_allowed, ACCESS_HELPER);\n6765:\t\tcase PTR_TO_MEM:\n6766:\t\t\tif (type_is_rdonly_mem(reg-\u003etype)) {\n6767:\t\t\t\tif (access_type == BPF_WRITE) {\n6768:\t\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n6769:\t\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n6770:\t\t\t\t\treturn -EACCES;\n6771:\t\t\t\t}\n6772:\t\t\t}\n6773:\t\t\treturn check_mem_region_access(env, reg, argno, 0,\n6774:\t\t\t\t\t\t       access_size, reg-\u003emem_size,\n6775:\t\t\t\t\t\t       zero_size_allowed);\n6776:\t\tcase PTR_TO_BUF:\n6777:\t\t\tif (type_is_rdonly_mem(reg-\u003etype)) {\n6778:\t\t\t\tif (access_type == BPF_WRITE) {\n6779:\t\t\t\t\tverbose(env, \"%s cannot write into %s\\n\",\n6780:\t\t\t\t\t\treg_arg_name(env, argno), reg_type_str(env, reg-\u003etype));\n6781:\t\t\t\t\treturn -EACCES;\n6782:\t\t\t\t}\n6783:\t\n6784:\t\t\t\tmax_access = \u0026env-\u003eprog-\u003eaux-\u003emax_rdonly_access;\n6785:\t\t\t} else {\n6786:\t\t\t\tmax_access = \u0026env-\u003eprog-\u003eaux-\u003emax_rdwr_access;\n6787:\t\t\t}\n6788:\t\t\treturn check_buffer_access(env, reg, argno, 0,\n6789:\t\t\t\t\t\t   access_size, zero_size_allowed,\n6790:\t\t\t\t\t\t   max_access);\n6791:\t\tcase PTR_TO_STACK:\n6792:\t\t\treturn check_stack_range_initialized(\n6793:\t\t\t\t\tenv, reg,\n6794:\t\t\t\t\targno, 0, access_size,\n6795:\t\t\t\t\tzero_size_allowed, access_type, meta);\n6796:\t\tcase PTR_TO_BTF_ID:\n6797:\t\t\treturn check_ptr_to_btf_access(env, regs, reg, argno, 0,\n6798:\t\t\t\t\t\t       access_size, access_type, -1);\n"
}

41/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23108
							output: 35
							thoughts: 36
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Diving into Stack Range Initialization**

Right, so we've hit a call to `check_stack_range_initialized`, and it's being passed an `access_type`. My immediate focus is on understanding *why* this function is being called in this context. What specific access type is driving the need for this stack range check? That's the first question. Now, I need to understand the function itself. What is `check_stack_range_initialized` actually *doing*? It's likely involved in validating or ensuring that certain regions of the stack are properly initialized before being accessed, which is typical for preventing memory errors, especially in low-level code. I'll need to look at the implementation of that function, probably along with the definition of `access_type`, to figure out the exact initialization semantics being enforced here. Is it protecting against stack overflows, uninitialized variable reads, or something else entirely? Time to dig in and see the details.



42/3 2026/08/11 11:32 tool grepper
0m Args:
{
  "Expression": "check_stack_range_initialized",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=3863=enum bpf_access_src {\n--\nkernel/bpf/verifier.c-3867-\nkernel/bpf/verifier.c:3868:static int check_stack_range_initialized(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\nkernel/bpf/verifier.c-3869-\t\t\t\t\t argno_t argno, int off, int access_size,\n--\nkernel/bpf/verifier.c=3892=static int check_stack_read_var_off(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-3900-\t */\nkernel/bpf/verifier.c:3901:\terr = check_stack_range_initialized(env, reg, ptr_argno, off, size,\nkernel/bpf/verifier.c-3902-\t\t\t\t\t    false, BPF_READ, NULL);\n--\nkernel/bpf/verifier.c=6575=static int check_atomic(struct bpf_verifier_env *env, struct bpf_insn *insn)\n--\nkernel/bpf/verifier.c-6617- */\nkernel/bpf/verifier.c:6618:static int check_stack_range_initialized(\nkernel/bpf/verifier.c-6619-\t\tstruct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int off,\n--\nkernel/bpf/verifier.c=6739=static int check_helper_mem_access(struct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno,\n--\nkernel/bpf/verifier.c-6791-\tcase PTR_TO_STACK:\nkernel/bpf/verifier.c:6792:\t\treturn check_stack_range_initialized(\nkernel/bpf/verifier.c-6793-\t\t\t\tenv, reg,\n--\nkernel/bpf/verifier.c=7695=static bool arg_type_is_raw_mem(enum bpf_arg_type type)\n--\nkernel/bpf/verifier.c-7699-\t * (uninitialized) memory argument, and like ARG_PTR_TO_MEM it may be\nkernel/bpf/verifier.c:7700:\t * passed as a PTR_TO_STACK that reaches check_stack_range_initialized().\nkernel/bpf/verifier.c-7701-\t */\n"
}

43/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24048
							output: 37
							thoughts: 21
						

Thoughts:
Okay, let's dive into this code.

**Analyzing `check_stack_range_initialized`**

Alright, my focus is now on `check_stack_range_initialized`, specifically starting at line 6618. I need to understand its role within this system. What's it checking? Is it for stack overflow protection, memory initialization, or perhaps something more nuanced related to concurrency or security? Given the name, I'm leaning towards range checks and initialization, but I need to see the context to be certain. My expertise tells me these kinds of checks are critical, and a thorough understanding of the logic is paramount, especially if the target is to debug, refactor or extend. Time to dig in and deconstruct this function.



44/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6618,
  "LineCount": 60
}

Results:
{
  "Contents": "6618:\tstatic int check_stack_range_initialized(\n6619:\t\t\tstruct bpf_verifier_env *env, struct bpf_reg_state *reg, argno_t argno, int off,\n6620:\t\t\tint access_size, bool zero_size_allowed,\n6621:\t\t\tenum bpf_access_type type, struct bpf_call_arg_meta *meta)\n6622:\t{\n6623:\t\tstruct bpf_func_state *state = bpf_func(env, reg);\n6624:\t\tint err, min_off, max_off, i, j, slot, spi;\n6625:\t\t/* Some accesses can write anything into the stack, others are\n6626:\t\t * read-only.\n6627:\t\t */\n6628:\t\tbool clobber = type == BPF_WRITE;\n6629:\t\t/*\n6630:\t\t * Negative access_size signals global subprog arg check where\n6631:\t\t * STACK_POISON slots are acceptable. static stack liveness\n6632:\t\t * might have determined that subprog doesn't read them,\n6633:\t\t * but BTF based global subprog validation isn't accurate enough.\n6634:\t\t */\n6635:\t\tbool allow_poison = access_size \u003c 0 || clobber;\n6636:\t\t/* The call will initialize the memory; uninitialized stack allowed */\n6637:\t\tbool raw_mode = meta \u0026\u0026 meta-\u003earg_raw_mem.regno == reg_from_argno(argno);\n6638:\t\n6639:\t\taccess_size = abs(access_size);\n6640:\t\n6641:\t\tif (access_size == 0 \u0026\u0026 !zero_size_allowed) {\n6642:\t\t\tverbose(env, \"invalid zero-sized read\\n\");\n6643:\t\t\treturn -EACCES;\n6644:\t\t}\n6645:\t\n6646:\t\terr = check_stack_access_within_bounds(env, reg, argno, off, access_size, type);\n6647:\t\tif (err)\n6648:\t\t\treturn err;\n6649:\t\n6650:\t\n6651:\t\tif (tnum_is_const(reg-\u003evar_off)) {\n6652:\t\t\tmin_off = max_off = reg-\u003evar_off.value + off;\n6653:\t\t} else {\n6654:\t\t\t/* Variable offset is prohibited for unprivileged mode for\n6655:\t\t\t * simplicity since it requires corresponding support in\n6656:\t\t\t * Spectre masking for stack ALU.\n6657:\t\t\t * See also retrieve_ptr_limit().\n6658:\t\t\t */\n6659:\t\t\tif (!env-\u003ebypass_spec_v1) {\n6660:\t\t\t\tchar tn_buf[48];\n6661:\t\n6662:\t\t\t\ttnum_strn(tn_buf, sizeof(tn_buf), reg-\u003evar_off);\n6663:\t\t\t\tverbose(env, \"%s variable offset stack access prohibited for !root, var_off=%s\\n\",\n6664:\t\t\t\t\treg_arg_name(env, argno), tn_buf);\n6665:\t\t\t\treturn -EACCES;\n6666:\t\t\t}\n6667:\t\t\t/* Only initialized buffer on stack is allowed to be accessed\n6668:\t\t\t * with variable offset. With uninitialized buffer it's hard to\n6669:\t\t\t * guarantee that whole memory is marked as initialized on\n6670:\t\t\t * helper return since specific bounds are unknown what may\n6671:\t\t\t * cause uninitialized stack leaking.\n6672:\t\t\t */\n6673:\t\t\traw_mode = false;\n6674:\t\n6675:\t\t\tmin_off = reg_smin(reg) + off;\n6676:\t\t\tmax_off = reg_smax(reg) + off;\n6677:\t\t}\n"
}

45/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25279
							output: 37
							thoughts: 218
						

Thoughts:
**Analyzing Memory Initialization in BPF Verification**

Okay, so I'm digging into this BPF verification logic, and I'm seeing a few things that are tripping me up. First, I'm trying to wrap my head around when `raw_mode` is actually active. It looks like it's true when `meta` has `arg_raw_mem` set and the register number matches the argument number. Makes sense, that's the raw memory access case. Right, `arg_raw_mem` gets set for those helper functions that explicitly take a pointer to uninitialized memory – that's the intended use. But here's the kicker: for kfuncs, `check_kfunc_args` *doesn't* set `arg_raw_mem`. That's where things get interesting.

Now, if `raw_mode` is *false*, and the access type (`type`) is `BPF_READ`, then the verifier checks if the memory on the stack is initialized. That's the normal behavior, what I'd expect. But wait... in `check_mem_size_reg`, it calls `check_helper_mem_access` *twice*: once with `BPF_READ` and then again with `BPF_WRITE`.

Okay, so what happens with `BPF_READ`? If we have `type == BPF_READ`, and `clobber` is false and we don't `allow_poison`, then, crucially, the verifier will check if the stack range is initialized. That's the bit I needed to clarify, especially when combined with the earlier point that `arg_raw_mem` is not being set for kfuncs, so now it makes sense why we have to check stack initialization within those contexts. Now, let's go deeper into the `check_stack_range_initialized` function and see what's actually happening at that level. I think I'm starting to get a good handle on this now.



46/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6678,
  "LineCount": 60
}

Results:
{
  "Contents": "6678:\t\n6679:\t\tif (raw_mode) {\n6680:\t\t\tmeta-\u003earg_raw_mem.size = access_size;\n6681:\t\t\treturn 0;\n6682:\t\t}\n6683:\t\n6684:\t\tfor (i = min_off; i \u003c max_off + access_size; i++) {\n6685:\t\t\tu8 *stype;\n6686:\t\n6687:\t\t\tslot = -i - 1;\n6688:\t\t\tspi = slot / BPF_REG_SIZE;\n6689:\t\t\tif (state-\u003eallocated_stack \u003c= slot) {\n6690:\t\t\t\tverbose(env, \"allocated_stack too small\\n\");\n6691:\t\t\t\treturn -EFAULT;\n6692:\t\t\t}\n6693:\t\n6694:\t\t\tstype = \u0026state-\u003estack[spi].slot_type[slot % BPF_REG_SIZE];\n6695:\t\t\tif (*stype == STACK_MISC)\n6696:\t\t\t\tgoto mark;\n6697:\t\t\tif ((*stype == STACK_ZERO) ||\n6698:\t\t\t    (*stype == STACK_INVALID \u0026\u0026 env-\u003eallow_uninit_stack)) {\n6699:\t\t\t\tif (clobber) {\n6700:\t\t\t\t\t/* helper can write anything into the stack */\n6701:\t\t\t\t\t*stype = STACK_MISC;\n6702:\t\t\t\t}\n6703:\t\t\t\tgoto mark;\n6704:\t\t\t}\n6705:\t\n6706:\t\t\tif (bpf_is_spilled_reg(\u0026state-\u003estack[spi]) \u0026\u0026\n6707:\t\t\t    (state-\u003estack[spi].spilled_ptr.type == SCALAR_VALUE ||\n6708:\t\t\t     env-\u003eallow_ptr_leaks)) {\n6709:\t\t\t\tif (clobber) {\n6710:\t\t\t\t\t__mark_reg_unknown(env, \u0026state-\u003estack[spi].spilled_ptr);\n6711:\t\t\t\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\n6712:\t\t\t\t\t\tscrub_spilled_slot(\u0026state-\u003estack[spi].slot_type[j]);\n6713:\t\t\t\t}\n6714:\t\t\t\tgoto mark;\n6715:\t\t\t}\n6716:\t\n6717:\t\t\tif (*stype == STACK_POISON) {\n6718:\t\t\t\tif (allow_poison)\n6719:\t\t\t\t\tgoto mark;\n6720:\t\t\t\tverbose(env, \"reading from stack %s off %d+%d size %d, slot poisoned by dead code elimination\\n\",\n6721:\t\t\t\t\treg_arg_name(env, argno), min_off, i - min_off, access_size);\n6722:\t\t\t} else if (tnum_is_const(reg-\u003evar_off)) {\n6723:\t\t\t\tverbose(env, \"invalid read from stack %s off %d+%d size %d\\n\",\n6724:\t\t\t\t\treg_arg_name(env, argno), min_off, i - min_off, access_size);\n6725:\t\t\t} else {\n6726:\t\t\t\tchar tn_buf[48];\n6727:\t\n6728:\t\t\t\ttnum_strn(tn_buf, sizeof(tn_buf), reg-\u003evar_off);\n6729:\t\t\t\tverbose(env, \"invalid read from stack %s var_off %s+%d size %d\\n\",\n6730:\t\t\t\t\treg_arg_name(env, argno), tn_buf, i - min_off, access_size);\n6731:\t\t\t}\n6732:\t\t\treturn -EACCES;\n6733:\tmark:\n6734:\t\t\t;\n6735:\t\t}\n6736:\t\treturn 0;\n6737:\t}\n"
}

47/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26951
							output: 31
							thoughts: 108
						

Thoughts:
**Analyzing Stack Initialization in BPF Read Operations**

Okay, so I'm looking at this condition: if the stack type is invalid and uninitialized stacks aren't allowed, we throw an "invalid read from stack" error and return `-EACCES`. This immediately tells me that if we're dealing with a `BPF_READ` operation, the stack *must* be initialized, which makes sense from a security perspective. But then, there's a caveat. I need to keep in mind that `env->allow_uninit_stack` gets set to true when the program has `CAP_PERFMON` or `CAP_SYS_ADMIN` capabilities, which kind of opens a door I need to be aware of. I need to trace back through the code to see precisely where and how `allow_uninit_stack` is being set to gain a fuller understanding of the security implications. That's the key: understand how the permissions affect stack access.



48/3 2026/08/11 11:32 tool grepper
0m Args:
{
  "Expression": "allow_uninit_stack",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/states.c=674=static bool is_stack_misc_after(struct bpf_verifier_env *env,\n--\nkernel/bpf/states.c-681-\t\t    ((stack-\u003eslot_type[i] == STACK_INVALID || stack-\u003eslot_type[i] == STACK_POISON) \u0026\u0026\nkernel/bpf/states.c:682:\t\t     env-\u003eallow_uninit_stack))\nkernel/bpf/states.c-683-\t\t\tcontinue;\n--\nkernel/bpf/states.c=702=static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,\n--\nkernel/bpf/states.c-735-\nkernel/bpf/states.c:736:\t\tif (env-\u003eallow_uninit_stack \u0026\u0026\nkernel/bpf/states.c-737-\t\t    old-\u003estack[spi].slot_type[i % BPF_REG_SIZE] == STACK_MISC)\n--\nkernel/bpf/verifier.c=3530=static int check_stack_write_var_off(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-3629-\t\tif ((*stype == STACK_INVALID || *stype == STACK_POISON) \u0026\u0026\nkernel/bpf/verifier.c:3630:\t\t    !env-\u003eallow_uninit_stack) {\nkernel/bpf/verifier.c-3631-\t\t\tverbose(env, \"uninit stack in range of var-offset write prohibited for !root; insn %d, off: %d\",\n--\nkernel/bpf/verifier.c=3715=static int check_stack_read_fixed_off(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-3780-\t\t\t\t\t}\nkernel/bpf/verifier.c:3781:\t\t\t\t\tif (type == STACK_INVALID \u0026\u0026 env-\u003eallow_uninit_stack)\nkernel/bpf/verifier.c-3782-\t\t\t\t\t\tcontinue;\n--\nkernel/bpf/verifier.c-3838-\t\t\t\tcontinue;\nkernel/bpf/verifier.c:3839:\t\t\tif (type == STACK_INVALID \u0026\u0026 env-\u003eallow_uninit_stack)\nkernel/bpf/verifier.c-3840-\t\t\t\tcontinue;\n--\nkernel/bpf/verifier.c=5981=static int check_stack_slot_within_bounds(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-5987-\nkernel/bpf/verifier.c:5988:\tif (t == BPF_WRITE || env-\u003eallow_uninit_stack)\nkernel/bpf/verifier.c-5989-\t\tmin_valid_off = -MAX_BPF_STACK;\n--\nkernel/bpf/verifier.c=6618=static int check_stack_range_initialized(\n--\nkernel/bpf/verifier.c-6697-\t\tif ((*stype == STACK_ZERO) ||\nkernel/bpf/verifier.c:6698:\t\t    (*stype == STACK_INVALID \u0026\u0026 env-\u003eallow_uninit_stack)) {\nkernel/bpf/verifier.c-6699-\t\t\tif (clobber) {\n--\nkernel/bpf/verifier.c=20113=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20138-\tenv-\u003eallow_ptr_leaks = bpf_allow_ptr_leaks(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c:20139:\tenv-\u003eallow_uninit_stack = bpf_allow_uninit_stack(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c-20140-\tenv-\u003ebypass_spec_v1 = bpf_bypass_spec_v1(env-\u003eprog-\u003eaux-\u003etoken);\n"
}

49/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28307
							output: 34
							thoughts: 15
						

Thoughts:
Okay, let's dive into this `bpf_allow_uninit_stack` thing.

**My Analysis of `bpf_allow_uninit_stack`**

Right, so I'm looking at `bpf_allow_uninit_stack`. This immediately triggers a mental checklist: security implications, performance considerations, and potential compatibility issues. My initial thought is, "Why are we even considering allowing uninitialized stack access in BPF?" This sounds inherently dangerous. In a trusted environment, maybe, but BPF is often running in contexts where untrusted code is a possibility. Exposing uninitialized stack memory seems like a glaring vulnerability, ripe for information leaks or manipulation.

I need to understand the motivation behind this change. Is it driven by a specific use case where performance is paramount and initializing the stack upfront is too costly? Are there architectural limitations in the current BPF implementation that necessitate this "feature"? I need to see the context. What problem is this supposed to solve?

Okay, let's quickly check the implementation. Is it done safely? Does this include any checks or workarounds that mitigate the obvious issues. Are there restrictions enforced that could help to limit the damage if it is actually allowed? I need to know how it's implemented. Is it a blanket allow-all, or is it gated behind some configuration flag or feature? What are the potential consequences of turning this "feature" on or off? Are there other features that interact with this setting?

I'm thinking about the potential attack vectors. Can a malicious BPF program be crafted to read sensitive data from the stack, perhaps remnants of previous function calls or kernel data? Could it be used to overwrite critical data structures, leading to privilege escalation or system instability? I need to think like an attacker here.

Furthermore, how does this affect existing BPF programs? Will they need to be modified to handle potentially uninitialized memory? Is there a risk of introducing subtle bugs or regressions in existing code? I need to consider the impact on the existing BPF ecosystem.

My immediate instinct is to err on the side of caution. Uninitialized stack memory is rarely a good thing, and I'll need a very compelling reason to support this change. I'm going to need to dig deep into the reasoning behind `bpf_allow_uninit_stack` and examine the proposed implementation very carefully. It better be rock solid.



50/3 2026/08/11 11:32 tool grepper
0m Args:
{
  "Expression": "bpf_allow_uninit_stack",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=20113=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20138-\tenv-\u003eallow_ptr_leaks = bpf_allow_ptr_leaks(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c:20139:\tenv-\u003eallow_uninit_stack = bpf_allow_uninit_stack(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c-20140-\tenv-\u003ebypass_spec_v1 = bpf_bypass_spec_v1(env-\u003eprog-\u003eaux-\u003etoken);\n"
}

51/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29047
							output: 29
							thoughts: 37
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating BPF Stack Initialization**

Right, so I need to understand how uninitialized stack data is handled in the BPF world. My first instinct, and the most logical place to start, is to check the core definitions and related system calls. I should probably dig into `include/linux/bpf.h`. That's where I expect to find the fundamental structures, flags, and macros related to BPF, and potentially the very definition of `bpf_allow_uninit_stack`, if it exists. If it doesn't exist, I expect there to be definitions in the same include that will give me clues to finding this.

If the header file doesn't offer direct answers, then I'll shift gears to the implementation side and examine the kernel's BPF system call handling. The file `kernel/bpf/syscall.c` should contain the logic for interpreting and processing BPF program loading, execution, and various operations. It's highly likely that any controls or settings regarding stack behavior would be handled or at least referenced in that part of the kernel code. Hopefully, I'll find the logic, or maybe even the existence or usage of a variable such as `bpf_allow_uninit_stack` to give me information on exactly how the uninitialized stack data is treated.



52/3 2026/08/11 11:32 tool grepper
0m Args:
{
  "Expression": "bpf_allow_uninit_stack",
  "PathPrefix": ""
}

Results:
{
  "Output": "include/linux/bpf.h=2862=static inline bool bpf_allow_ptr_leaks(const struct bpf_token *token)\n--\ninclude/linux/bpf.h-2866-\ninclude/linux/bpf.h:2867:static inline bool bpf_allow_uninit_stack(const struct bpf_token *token)\ninclude/linux/bpf.h-2868-{\n--\nkernel/bpf/verifier.c=20113=int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,\n--\nkernel/bpf/verifier.c-20138-\tenv-\u003eallow_ptr_leaks = bpf_allow_ptr_leaks(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c:20139:\tenv-\u003eallow_uninit_stack = bpf_allow_uninit_stack(env-\u003eprog-\u003eaux-\u003etoken);\nkernel/bpf/verifier.c-20140-\tenv-\u003ebypass_spec_v1 = bpf_bypass_spec_v1(env-\u003eprog-\u003eaux-\u003etoken);\n"
}

53/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29710
							output: 37
							thoughts: 15
						

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

**Initial Assessment of the Task**

Alright, the directive is to dissect and summarize the implications of `bpf_allow_uninit_stack`. My immediate thought is, this likely pertains to BPF (Berkeley Packet Filter) and some aspect of stack initialization within that environment. Given the "allow_uninit" part, I suspect we're delving into a potential security trade-off. Allowing uninitialized stack data in a BPF program is a well-known risk, so it's a key function to understand. It has implications for vulnerability assessments, and performance optimization considerations. I'm going to carefully read it.



54/3 2026/08/11 11:32 tool read-file
0m Args:
{
  "File": "include/linux/bpf.h",
  "FirstLine": 2867,
  "LineCount": 10
}

Results:
{
  "Contents": "2867:\tstatic inline bool bpf_allow_uninit_stack(const struct bpf_token *token)\n2868:\t{\n2869:\t\treturn bpf_token_capable(token, CAP_PERFMON);\n2870:\t}\n2871:\t\n2872:\tstatic inline bool bpf_bypass_spec_v1(const struct bpf_token *token)\n2873:\t{\n2874:\t\treturn bpf_jit_bypass_spec_v1() ||\n2875:\t\t\tcpu_mitigations_off() ||\n2876:\t\t\tbpf_token_capable(token, CAP_PERFMON);\n"
}

55/3 2026/08/11 11:32 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30101
							output: 37
							thoughts: 3584
						

Thoughts:
**Uninitialized Memory Leak in BPF Programs with CAP_PERFMON: A Deep Dive**

Okay, here's the situation as I see it. If a BPF program has `CAP_PERFMON` and the verifier has `allow_uninit_stack` enabled, which it does in this case, then we have a potential vulnerability. The verifier won't complain about uninitialized stack memory. It will mark it as `STACK_MISC` if there is a clobber. If `allow_uninit_stack` is set, the verifier permits uninitialized stack memory to be passed to a kfunc.

Let's break down the `check_stack_range_initialized` function. If a stack slot is `STACK_ZERO` or `STACK_INVALID` and `allow_uninit_stack` is true, and there is a clobber, the type will be changed to `STACK_MISC`. The clobber happens if the helper can write to the stack. We can see that the `clobber` is `true` if `type == BPF_WRITE`. In `check_mem_size_reg`, it checks this by calling `check_helper_mem_access` with `BPF_READ` and then `BPF_WRITE`. For `BPF_READ`, `clobber` is false. If the value is `STACK_INVALID`, it just goes to mark and returns 0. For `BPF_WRITE`, clobber is true, and it sets `*stype = STACK_MISC`. Therefore, the verifier allows passing uninitialized stack memory to the kfunc if `CAP_PERFMON` is present!

The problem is that if the verifier allows the memory to be passed uninitialized and the kfunc *reads* from it, it's an uninitialized memory *read*. Let's check `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`. These kfuncs *write* to the destination (`dst`), not read from it. The verifier marks the destination as initialized! However, there is an issue with `bpf_copy_from_user_mm_str` if the function fails and `BPF_F_PAD_ZEROS` isn't set, because the destination might be left uninitialized. If `BPF_F_PAD_ZEROS` is not set, meaning `flags` is zero, and if `copy_remote_mm_str` returns an error, `dst` is left uninitialized! However, the verifier thinks it's written because the `BPF_WRITE` check marked it as `STACK_MISC`.

`check_mem_size_reg` is called for `KF_ARG_MEM_SIZE` and `bpf_copy_from_user_mm_str` uses `KF_ARG_MEM_SIZE` because `dst` is `KF_ARG_PTR_TO_MEM`, `dst__sz` is `KF_ARG_MEM_SIZE`, so the verifier will mark the destination (`dst`) as initialized after the call. So the BPF program can then read from a buffer that's actually uninitialized, which is an uninitialized memory leak! If the BPF program reads from `dst`, it is reading uninitialized kernel stack memory, and the program can then leak that memory to user-space through maps or ring buffers.

Fortunately, KMSAN will detect this because it tracks reads of uninitialized memory. If the BPF program reads from `dst` after `bpf_copy_from_user_mm_str` fails, KMSAN will flag it as a use of uninitialized value! Let's check the code of the `bpf_copy_from_user_mm_str` to verify this. If `flags == 0`, the function proceeds. If `copy_remote_mm_str` returns `< 0`, it goes to `err`, and returns `ret`, and `dst` is not zeroed. If it returns `>= 0`, it returns `ret + 1`.

The function copies `ret` bytes. Bytes from `ret` to `dst__sz` are not initialized. However, the verifier marks the entire destination up to `dst__sz` as `STACK_MISC` which is initialized. The verifier does not know about the `BPF_F_PAD_ZEROS` flag, so it assumes the entire buffer is initialized. Thus, with `flags = 0`, a BPF program can call `bpf_copy_from_user_mm_str` and then read `dst[sizeof(dst) - 1]`, which is uninitialized kernel stack memory, and then write it to a


56/3 2026/08/11 11:33 tool grepper
0m Args:
{
  "Expression": "is_kfunc_arg_uninit",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=10768=static bool is_kfunc_arg_alloc_obj(const struct btf *btf, const struct btf_param *arg)\n--\nkernel/bpf/verifier.c-10772-\nkernel/bpf/verifier.c:10773:static bool is_kfunc_arg_uninit(const struct btf *btf, const struct btf_param *arg)\nkernel/bpf/verifier.c-10774-{\n--\nkernel/bpf/verifier.c=12008=static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,\n--\nkernel/bpf/verifier.c-12219-\nkernel/bpf/verifier.c:12220:\t\t\tif (is_kfunc_arg_uninit(btf, \u0026args[i]))\nkernel/bpf/verifier.c-12221-\t\t\t\tdynptr_arg_type |= MEM_UNINIT;\n--\nkernel/bpf/verifier.c=12708=s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-12762-\t\treturn -size;\nkernel/bpf/verifier.c:12763:\tif (is_kfunc_arg_uninit(btf, \u0026args[arg]))\nkernel/bpf/verifier.c-12764-\t\treturn -size;\n"
}

57/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35151
							output: 38
							thoughts: 301
						

Thoughts:
**Analyzing Kernel Function Argument Initialization**

Okay, so I'm digging into how we handle initialization of arguments for kernel functions, specifically those related to memory pointers. My initial thought was that `is_kfunc_arg_uninit` was only employed for `KF_ARG_PTR_TO_DYNPTR` within the `check_kfunc_args` function. But then I realized, hang on, what about `KF_ARG_PTR_TO_MEM`? That should also be considered.

Looking closer at `check_kfunc_args`, I see that the code flow for `KF_ARG_PTR_TO_MEM` is a little different. When `kf_arg_type` has the `MEM_FIXED_SIZE` flag set, it calls `check_mem_reg`, which does handle memory region validation.

However, the real puzzle is with `KF_ARG_MEM_SIZE`. This case calls `check_mem_size_reg`. And, I now see, `check_mem_size_reg` is *always* invoked with `BPF_READ | BPF_WRITE` for `KF_ARG_MEM_SIZE`, which is interesting.

Here's the problem: there's no explicit check for `__uninit` being passed on to `check_mem_size_reg` when dealing with `KF_ARG_PTR_TO_MEM`! We may have a potentially uninitialized memory pointer passing under the radar.

Okay, time to examine the rest of `check_kfunc_args` and its handling of `__uninit` more closely when dealing with `KF_ARG_PTR_TO_MEM`. We need to be sure.



58/3 2026/08/11 11:33 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 12415,
  "LineCount": 40
}

Results:
{
  "Contents": "12415:\t\t\tcase KF_ARG_MEM_SIZE:\n12416:\t\t\t{\n12417:\t\t\t\tstruct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);\n12418:\t\t\t\tstruct bpf_reg_state *size_reg = reg;\n12419:\t\t\t\targno_t buff_argno = argno_from_arg(i);\n12420:\t\n12421:\t\t\t\tif (reg-\u003etype != SCALAR_VALUE) {\n12422:\t\t\t\t\tverbose(env, \"%s is not a scalar\\n\", reg_arg_name(env, argno));\n12423:\t\t\t\t\treturn -EINVAL;\n12424:\t\t\t\t}\n12425:\t\n12426:\t\t\t\tif (bpf_register_is_null(buff_reg))\n12427:\t\t\t\t\tbreak;\n12428:\t\n12429:\t\t\t\tret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,\n12430:\t\t\t\t\t\t\t BPF_READ | BPF_WRITE, true, meta);\n12431:\t\t\t\tif (ret \u003c 0) {\n12432:\t\t\t\t\tverbose(env, \"%s and \", reg_arg_name(env, buff_argno));\n12433:\t\t\t\t\tverbose(env, \"%s memory, len pair leads to invalid memory access\\n\",\n12434:\t\t\t\t\t\treg_arg_name(env, argno));\n12435:\t\t\t\t\treturn ret;\n12436:\t\t\t\t}\n12437:\t\t\t\tbreak;\n12438:\t\t\t}\n12439:\t\t\tcase KF_ARG_PTR_TO_CALLBACK:\n12440:\t\t\t\tif (reg-\u003etype != PTR_TO_FUNC) {\n12441:\t\t\t\t\tverbose(env, \"%s expected pointer to func\\n\", reg_arg_name(env, argno));\n12442:\t\t\t\t\treturn -EINVAL;\n12443:\t\t\t\t}\n12444:\t\t\t\tmeta-\u003esubprogno = reg-\u003esubprogno;\n12445:\t\t\t\tbreak;\n12446:\t\t\tcase KF_ARG_PTR_TO_REFCOUNTED_KPTR:\n12447:\t\t\t\tif (!type_is_ptr_alloc_obj(reg-\u003etype)) {\n12448:\t\t\t\t\tverbose(env, \"%s is neither owning or non-owning ref\\n\",\n12449:\t\t\t\t\t\treg_arg_name(env, argno));\n12450:\t\t\t\t\treturn -EINVAL;\n12451:\t\t\t\t}\n12452:\t\t\t\tif (!type_is_non_owning_ref(reg-\u003etype))\n12453:\t\t\t\t\tmeta-\u003earg_owning_ref = true;\n12454:\t\n"
}

59/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36584
							output: 40
							thoughts: 1184
						

Thoughts:
**Uninitialized Memory Leak in Privileged BPF Programs**

Okay, so `KF_ARG_MEM_SIZE` consistently uses `BPF_READ | BPF_WRITE`. If it uses `BPF_WRITE`, the verifier enforces that the buffer is initialized *before* the kfunc call, which is true for unprivileged users, or privileged users without `CAP_PERFMON`.  But if the user *must* initialize the buffer, then it *is* initialized, right? Then if `bpf_copy_from_user_mm_str` fails to fully initialize it, that's not a problem, because the unwritten part is *already* initialized (e.g., with zeros).

Alright, let's step back. An unprivileged BPF program *must* initialize the buffer. If it initializes the buffer (like, `char buf[64] = {};`), it's *fully* initialized. `bpf_copy_from_user_mm_str` writes into it. If the copy fails, the rest remains zero. No uninitialized memory issues there.

But, what if the BPF program *is* privileged (with `CAP_PERFMON`)? In that case, `env->allow_uninit_stack` is true. The verifier *allows* passing an *uninitialized* buffer.  It *then* marks the buffer as initialized (`STACK_MISC`) *after* the kfunc call because of the `BPF_WRITE`. However, `bpf_copy_from_user_mm_str` *might not* initialize the whole buffer if `flags = 0`. So, the buffer is left partially uninitialized! And then the BPF program could read that uninitialized part and leak it!

Is that a bug? Yes, it's a bug! Privileged BPF programs can leak kernel stack memory that hasn't been initialized.  Now, the security boundary... usually `CAP_PERFMON` allows reading kernel memory anyway (with `bpf_probe_read_kernel`). But leaking uninitialized kernel stack memory is still a bug. KMSAN *will* catch that! KMSAN doesn't care about privileges; if it reads uninitialized memory, it reports it. And syzbot runs with root, so it'll definitely trigger this!

Wait, let's re-examine the patch...

```c
+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+					  const void __user *unsafe_ptr__ign,
+					  struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,
+				 dst, dst__sz, 0);
+	if (ret < 0)
+		goto err;
+
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst + ret, 0, dst__sz - ret);
+
+	return ret + 1;
+
+err:
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst, 0, dst__sz);
+	return ret;
+}
```

Okay, if `flags` is zero, and `copy_remote_mm_str` returns `ret >= 0`, the helper returns `ret + 1`. The BPF program *knows* only `ret + 1` bytes are initialized! If the program reads beyond `ret + 1`, it's reading uninitialized memory. But the verifier *marked* the *entire* `dst__sz` as initialized (`STACK_MISC`)! So, the verifier is *allowing* the BPF program to read beyond the initialized region! KMSAN will catch it, of course.

Is this a verifier bug or a kfunc bug?  The verifier marks the entire buffer initialized because `check_mem_size_reg` uses `reg_umax(size_reg)` which is equal to `dst__sz` for `BPF_WRITE`. This is a known verifier limitation: if a helper/kfunc writes to a buffer, it assumes the *entire* buffer is written to (or at least, marks it that way). To prevent leaks, helpers *must* initialize the *entire*


60/3 2026/08/11 11:33 tool grepper
0m Args:
{
  "Expression": "bpf_probe_read_user_str",
  "PathPrefix": "kernel/trace/bpf_trace.c"
}

Results:
{
  "Output": "kernel/trace/bpf_trace.c=229=static __always_inline int\nkernel/trace/bpf_trace.c:230:bpf_probe_read_user_str_common(void *dst, u32 size,\nkernel/trace/bpf_trace.c-231-\t\t\t       const void __user *unsafe_ptr)\n--\nkernel/trace/bpf_trace.c-250-\nkernel/trace/bpf_trace.c:251:BPF_CALL_3(bpf_probe_read_user_str, void *, dst, u32, size,\nkernel/trace/bpf_trace.c-252-\t   const void __user *, unsafe_ptr)\nkernel/trace/bpf_trace.c-253-{\nkernel/trace/bpf_trace.c:254:\treturn bpf_probe_read_user_str_common(dst, size, unsafe_ptr);\nkernel/trace/bpf_trace.c-255-}\nkernel/trace/bpf_trace.c-256-\nkernel/trace/bpf_trace.c:257:const struct bpf_func_proto bpf_probe_read_user_str_proto = {\nkernel/trace/bpf_trace.c:258:\t.func\t\t= bpf_probe_read_user_str,\nkernel/trace/bpf_trace.c-259-\t.gpl_only\t= true,\n--\nkernel/trace/bpf_trace.c=336=BPF_CALL_3(bpf_probe_read_compat_str, void *, dst, u32, size,\n--\nkernel/trace/bpf_trace.c-339-\tif ((unsigned long)unsafe_ptr \u003c TASK_SIZE) {\nkernel/trace/bpf_trace.c:340:\t\treturn bpf_probe_read_user_str_common(dst, size,\nkernel/trace/bpf_trace.c-341-\t\t\t\t(__force void __user *)unsafe_ptr);\n--\nkernel/trace/bpf_trace.c=3712=__bpf_kfunc int bpf_probe_read_kernel_dynptr(const struct bpf_dynptr *dptr, u64 off,\n--\nkernel/trace/bpf_trace.c-3718-\nkernel/trace/bpf_trace.c:3719:__bpf_kfunc int bpf_probe_read_user_str_dynptr(const struct bpf_dynptr *dptr, u64 off,\nkernel/trace/bpf_trace.c-3720-\t\t\t\t\t       u64 size, const void __user *unsafe_ptr__ign)\n"
}

61/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39507
							output: 38
							thoughts: 18
						

Thoughts:
Okay, let's dive into `bpf_probe_read_user_str_common`. My expertise tells me this function is a critical piece within the BPF (Berkeley Packet Filter) ecosystem, likely handling the delicate task of safely reading strings from user space. The "probe" part immediately suggests it's designed for introspection and debugging, which is a key aspect of BPF's power. It's designed to inspect the behavior of user applications without causing any disruption.

The "read_user" part, paired with "str," confirms the function's focus on string manipulation from user space. This is where things get interesting, because reading directly from user memory is inherently dangerous. Unvalidated pointers and malformed data could lead to kernel panics or security vulnerabilities. I'm expecting this function to be meticulously engineered with robust error handling and boundary checks to mitigate those risks. My intuition tells me to expect it to utilize mechanisms like address space checking, length validation, and potentially even page fault handling within the BPF context.

The "common" suffix implies that this likely sits as a shared component, potentially called by more specialized string reading functions. This means it probably implements the core logic for the read operation, while other functions might handle specific nuances or variations on the user-space string format. I'm expecting to see careful consideration of edge cases like null-terminated strings, truncated strings, and strings with unexpected characters. I’m thinking there might be safety checks that are performed based on the maximum allowed string length, as well. Let's see what security aspects are employed. Time to go deeper.



62/3 2026/08/11 11:33 tool read-file
0m Args:
{
  "File": "kernel/trace/bpf_trace.c",
  "FirstLine": 229,
  "LineCount": 30
}

Results:
{
  "Contents": " 229:\tstatic __always_inline int\n 230:\tbpf_probe_read_user_str_common(void *dst, u32 size,\n 231:\t\t\t\t       const void __user *unsafe_ptr)\n 232:\t{\n 233:\t\tint ret;\n 234:\t\n 235:\t\t/*\n 236:\t\t * NB: We rely on strncpy_from_user() not copying junk past the NUL\n 237:\t\t * terminator into `dst`.\n 238:\t\t *\n 239:\t\t * strncpy_from_user() does long-sized strides in the fast path. If the\n 240:\t\t * strncpy does not mask out the bytes after the NUL in `unsafe_ptr`,\n 241:\t\t * then there could be junk after the NUL in `dst`. If user takes `dst`\n 242:\t\t * and keys a hash map with it, then semantically identical strings can\n 243:\t\t * occupy multiple entries in the map.\n 244:\t\t */\n 245:\t\tret = strncpy_from_user_nofault(dst, unsafe_ptr, size);\n 246:\t\tif (unlikely(ret \u003c 0))\n 247:\t\t\tmemset(dst, 0, size);\n 248:\t\treturn ret;\n 249:\t}\n 250:\t\n 251:\tBPF_CALL_3(bpf_probe_read_user_str, void *, dst, u32, size,\n 252:\t\t   const void __user *, unsafe_ptr)\n 253:\t{\n 254:\t\treturn bpf_probe_read_user_str_common(dst, size, unsafe_ptr);\n 255:\t}\n 256:\t\n 257:\tconst struct bpf_func_proto bpf_probe_read_user_str_proto = {\n 258:\t\t.func\t\t= bpf_probe_read_user_str,\n"
}

63/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40417
							output: 34
							thoughts: 176
						

Thoughts:
**Analyzing `bpf_probe_read_user_str_common` and `strncpy_from_user_nofault`**

Okay, hold on a second. Something's nagging at me about `bpf_probe_read_user_str_common`. Looking at the code, it's pretty clear that if `strncpy_from_user_nofault` returns a negative value, it zeroes out the entire destination buffer (`dst`). But, and this is the important bit, what happens when `strncpy_from_user_nofault` *succeeds* or returns a non-negative value? It doesn't look like the remaining portion of `dst` is zeroed.

Now, let's step back and consider `strncpy_from_user_nofault` itself. It copies data from user space, up to a specified `size`. It's designed to stop at a null terminator, that much is clear. But does it *guarantee* that the rest of the buffer is zeroed if it finds a null terminator before it hits size? No. This seems like a potential security vulnerability. I need to dive deeper into how `strncpy_from_user_nofault` behaves in those cases to verify this assumption. I'm going to look at the implementation of `strncpy_from_user_nofault` to be sure.



64/3 2026/08/11 11:33 tool grepper
0m Args:
{
  "Expression": "strncpy_from_user_nofault",
  "PathPrefix": "mm/maccess.c"
}

Results:
{
  "Output": "mm/maccess.c=163=EXPORT_SYMBOL_GPL(copy_to_user_nofault);\n--\nmm/maccess.c-165-/**\nmm/maccess.c:166: * strncpy_from_user_nofault: - Copy a NUL terminated string from unsafe user\nmm/maccess.c-167- *\t\t\t\taddress.\n--\nmm/maccess.c-182- */\nmm/maccess.c:183:long strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,\nmm/maccess.c-184-\t\t\t      long count)\n"
}

65/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41087
							output: 34
							thoughts: 14
						

Thoughts:
Okay, let's dive into this `strncpy_from_user_nofault` function.

**Analyzing `strncpy_from_user_nofault` - Let's Get into the Details**

Right, `strncpy_from_user_nofault`. The name itself hints at its purpose: a safe way to copy a string from user space, likely meant to mitigate the usual faults that can arise from direct user memory access. The "nofault" suffix is the key here. It suggests a focus on preventing the kernel from panicking or entering an unrecoverable state if the user-provided address is invalid or points to inaccessible memory. This is critical for security and stability.

I'm immediately thinking about how it handles potential user-space errors. Is it using something like `access_ok` for pre-flight checks? How robust are these checks? Are they complete? I'll need to see the implementation details to assess the level of protection. The fact that it's `strncpy` means it's a fixed-length copy, so I'll need to see how it manages potential truncation. I assume it's handling the null termination correctly, maybe adding an extra check. This is an important consideration as an off-by-one error or a failure to null terminate correctly can lead to various security vulnerabilities.

I'm curious how it differs from a more general copy function, especially the trade-offs it has made in terms of performance and security. Is there any form of optimistic locking or optimistic copy being employed? I anticipate that it might be slower than a non-safe equivalent, because of the fault handling overhead. But the cost of this safety is essential.

I will also be interested in how the function is used in the codebase. Are there any common or unusual patterns? Looking at its call sites is usually a good indicator of what sort of security threats the function is meant to address, and it would be really helpful in informing my security review of this function.



66/3 2026/08/11 11:33 tool read-file
0m Args:
{
  "File": "mm/maccess.c",
  "FirstLine": 183,
  "LineCount": 30
}

Results:
{
  "Contents": " 183:\tlong strncpy_from_user_nofault(char *dst, const void __user *unsafe_addr,\n 184:\t\t\t\t      long count)\n 185:\t{\n 186:\t\tlong ret;\n 187:\t\n 188:\t\tif (unlikely(count \u003c= 0))\n 189:\t\t\treturn 0;\n 190:\t\n 191:\t\tpagefault_disable();\n 192:\t\tret = strncpy_from_user(dst, unsafe_addr, count);\n 193:\t\tpagefault_enable();\n 194:\t\n 195:\t\tif (ret \u003e= count) {\n 196:\t\t\tret = count;\n 197:\t\t\tdst[ret - 1] = '\\0';\n 198:\t\t} else if (ret \u003e= 0) {\n 199:\t\t\tret++;\n 200:\t\t}\n 201:\t\n 202:\t\treturn ret;\n 203:\t}\n 204:\t\n 205:\t/**\n 206:\t * strnlen_user_nofault: - Get the size of a user string INCLUDING final NUL.\n 207:\t * @unsafe_addr: The string to measure.\n 208:\t * @count: Maximum count (including NUL)\n 209:\t *\n 210:\t * Get the size of a NUL-terminated string in user space without pagefault.\n 211:\t *\n 212:\t * Returns the size of the string INCLUDING the terminating NUL.\n"
}

67/3 2026/08/11 11:33 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41962
							output: 33
							thoughts: 407
						

Thoughts:
**My Deep Dive into `bpf_probe_read_user_str` and Buffer Initialization**

Okay, here's what I'm thinking... it just hit me: `strncpy_from_user` *doesn't* zero the rest of the buffer. That means `bpf_probe_read_user_str`, as an underlying function, *also* won't zero the remaining parts of the buffer.

Hold on... if `bpf_probe_read_user_str` doesn't zero the end of the buffer, then it also leaves the rest of the buffer uninitialized. This is important!

But, hang on, `bpf_probe_read_user_str` is a BPF helper, not a kfunc. This changes things. I need to understand how the BPF verifier handles this. Let's dig into the code and see what `bpf_probe_read_user_str_proto` says:

```c
const struct bpf_func_proto bpf_probe_read_user_str_proto = {
	.func		= bpf_probe_read_user_str,
	.gpl_only	= true,
	.ret_type	= RET_INTEGER,
	.arg1_type	= ARG_PTR_TO_UNINIT_MEM,
	.arg2_type	= ARG_CONST_SIZE_OR_ZERO,
	.arg3_type	= ARG_ANYTHING,
};
```

Aha! Look at that: `arg1_type` is defined as `ARG_PTR_TO_UNINIT_MEM`. This is key!

Wait... if it's `ARG_PTR_TO_UNINIT_MEM`, the verifier must set `raw_mode = true` in `check_stack_range_initialized`.

And if `raw_mode = true`, that means the verifier doesn't *require* the buffer to be initialized before the call!

But here's the kicker: if it doesn't *require* it to be initialized, then after the call, the verifier must mark the *entire* buffer as `STACK_MISC` (which means initialized). This could have implications for how data is handled later. I'd better double check `check_helper_call` in `verifier.c` to see how it's handled.



68/3 2026/08/11 11:34 tool grepper
0m Args:
{
  "Expression": "check_helper_call",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=10192=static int release_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-10209-\nkernel/bpf/verifier.c:10210:static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\nkernel/bpf/verifier.c-10211-\t\t\t     int *insn_idx_p)\n--\nkernel/bpf/verifier.c=17243=static int do_check_insn(struct bpf_verifier_env *env, bool *do_print_state)\n--\nkernel/bpf/verifier.c-17312-\t\t\t\treturn check_kfunc_call(env, insn, \u0026env-\u003einsn_idx);\nkernel/bpf/verifier.c:17313:\t\t\treturn check_helper_call(env, insn, \u0026env-\u003einsn_idx);\nkernel/bpf/verifier.c-17314-\t\t} else if (opcode == BPF_JA) {\n"
}

69/3 2026/08/11 11:34 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 43215
							output: 38
							thoughts: 28
						

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

**Analyzing `check_helper_call`'s Handling of `ARG_PTR_TO_UNINIT_MEM`**

Okay, so I need to understand how the system manages uninitialized memory pointers, specifically when they're passed as arguments to a function via `check_helper_call`. This is crucial for avoiding potential vulnerabilities like information leaks or crashes. Let's dive into the `check_helper_call` function itself. I want to see precisely how it interprets and handles that specific `ARG_PTR_TO_UNINIT_MEM` flag. Is it just a warning, or does it trigger some more robust analysis? Does the code attempt to track the potential flow of uninitialized data? Does it try to sanitize, or at least flag, potential uses of these arguments? I'm particularly interested in how it interacts with the underlying memory model and potential aliasing scenarios. My aim is to grasp the extent of the protections implemented, and if they are sufficient to cover corner cases. Let's see if this function is as thorough as I hope.



70/3 2026/08/11 11:34 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 10340,
  "LineCount": 60
}

Results:
{
  "Contents": "10340:\t\t\tbreak;\n10341:\t\tcase BPF_FUNC_snprintf:\n10342:\t\t\terr = check_bpf_snprintf_call(env, regs);\n10343:\t\t\tbreak;\n10344:\t\tcase BPF_FUNC_loop:\n10345:\t\t\tupdate_loop_inline_state(env, meta.subprogno);\n10346:\t\t\t/* Verifier relies on R1 value to determine if bpf_loop() iteration\n10347:\t\t\t * is finished, thus mark it precise.\n10348:\t\t\t */\n10349:\t\t\terr = mark_chain_precision(env, BPF_REG_1);\n10350:\t\t\tif (err)\n10351:\t\t\t\treturn err;\n10352:\t\t\tif (cur_func(env)-\u003ecallback_depth \u003c reg_umax(\u0026regs[BPF_REG_1])) {\n10353:\t\t\t\terr = push_callback_call(env, insn, insn_idx, meta.subprogno,\n10354:\t\t\t\t\t\t\t set_loop_callback_state);\n10355:\t\t\t} else {\n10356:\t\t\t\tcur_func(env)-\u003ecallback_depth = 0;\n10357:\t\t\t\tif (env-\u003elog.level \u0026 BPF_LOG_LEVEL2)\n10358:\t\t\t\t\tverbose(env, \"frame%d bpf_loop iteration limit reached\\n\",\n10359:\t\t\t\t\t\tenv-\u003ecur_state-\u003ecurframe);\n10360:\t\t\t}\n10361:\t\t\tbreak;\n10362:\t\tcase BPF_FUNC_dynptr_from_mem:\n10363:\t\t\tif (regs[BPF_REG_1].type != PTR_TO_MAP_VALUE) {\n10364:\t\t\t\tverbose(env, \"Unsupported reg type %s for bpf_dynptr_from_mem data\\n\",\n10365:\t\t\t\t\treg_type_str(env, regs[BPF_REG_1].type));\n10366:\t\t\t\treturn -EACCES;\n10367:\t\t\t}\n10368:\t\t\tbreak;\n10369:\t\tcase BPF_FUNC_set_retval:\n10370:\t\t{\n10371:\t\t\tstruct bpf_retval_range range = {\n10372:\t\t\t\t.minval = -MAX_ERRNO,\n10373:\t\t\t\t.maxval = 0,\n10374:\t\t\t\t.return_32bit = true\n10375:\t\t\t};\n10376:\t\t\tstruct bpf_reg_state *r1 = \u0026regs[BPF_REG_1];\n10377:\t\n10378:\t\t\tif (r1-\u003etype != SCALAR_VALUE) {\n10379:\t\t\t\tverbose(env, \"R1 is not a scalar\\n\");\n10380:\t\t\t\treturn -EINVAL;\n10381:\t\t\t}\n10382:\t\n10383:\t\t\t/* CGROUP_GETSOCKOPT is allowed to return arbitrary value */\n10384:\t\t\tif (prog_type == BPF_PROG_TYPE_CGROUP_SOCKOPT \u0026\u0026\n10385:\t\t\t    env-\u003eprog-\u003eexpected_attach_type == BPF_CGROUP_GETSOCKOPT)\n10386:\t\t\t\tbreak;\n10387:\t\n10388:\t\t\tif (prog_type == BPF_PROG_TYPE_LSM \u0026\u0026\n10389:\t\t\t    env-\u003eprog-\u003eexpected_attach_type == BPF_LSM_CGROUP) {\n10390:\t\t\t\tif (!env-\u003eprog-\u003eaux-\u003eattach_func_proto-\u003etype) {\n10391:\t\t\t\t\t/* Make sure programs that attach to void\n10392:\t\t\t\t\t * hooks don't try to modify return value.\n10393:\t\t\t\t\t */\n10394:\t\t\t\t\tverbose(env, \"BPF_LSM_CGROUP that attach to void LSM hooks can't modify return value!\\n\");\n10395:\t\t\t\t\treturn -EINVAL;\n10396:\t\t\t\t}\n10397:\t\t\t\tbpf_lsm_get_retval_range(env-\u003eprog, \u0026range);\n10398:\t\t\t}\n10399:\t\n"
}

71/3 2026/08/11 11:34 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44616
							output: 33
							thoughts: 24
						

Thoughts:
Here's my thought process, as if I were analyzing that code:

**Analyzing `ARG_PTR_TO_UNINIT_MEM`'s Impact on Stack State**

Okay, I'm diving in. I need to understand exactly how `ARG_PTR_TO_UNINIT_MEM` modifies the stack state. I'm already pretty familiar with memory management, especially in this context, so the goal here is to pinpoint the specific actions that this particular operation performs. What's it doing with that pointer? Is it merely reading from an uninitialized location, or is it potentially writing to it, which would be far more interesting (and dangerous, depending on the architecture and surrounding code)? I need to trace this operation's effect on stack frame layout – does it potentially overwrite return addresses or other vital stack data? The real "aha!" moment will come when I see how `ARG_PTR_TO_UNINIT_MEM` interacts with other operations *around* it. Understanding the before/after stack snapshot is the key. Is this operation setting up a potential vulnerability, or is it intended behavior? The details matter – so let's get into them.



72/3 2026/08/11 11:34 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "update_stack_depth",
  "PathPrefix": "kernel/bpf/verifier.c"
}

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

73/3 2026/08/11 11:34 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44930
							output: 33
							thoughts: 28
						

Thoughts:
Here's my thought process, as if I were considering those search terms:

**Delving into Memory Management and Stack Operations**

Okay, I need to get to the bottom of this. Let's start with a few key search terms that jump out based on my experience. I'm thinking about memory management, specifically, how values on the stack are handled. I'm going to look for implementations of `update_stack_val`. This likely points to how a value residing on the stack gets modified, which could be critical for understanding a bug or performance bottleneck.

Alternatively, I could try searching for `mark_stack_misc`. This suggests we're dealing with tagging memory on the stack for miscellaneous reasons. Perhaps it indicates a garbage collection scheme, memory protection, or a debugging strategy related to the stack. Understanding how the memory on the stack is marked could provide insight into what data is considered special or transient.

Finally, `arg_raw_mem` catches my attention. This smells like we're dealing with arguments passed directly to functions, likely involving raw memory or pointer manipulations. This seems related to the stack and how functions receive input, so it's a good place to start the investigation. These terms are all pointing to stack-related operations, and digging into them seems like a logical next step to understand the behavior of the system.



74/3 2026/08/11 11:34 tool grepper
0m Args:
{
  "Expression": "arg_raw_mem",
  "PathPrefix": "kernel/bpf/verifier.c"
}

Results:
{
  "Output": "kernel/bpf/verifier.c=6618=static int check_stack_range_initialized(\n--\nkernel/bpf/verifier.c-6636-\t/* The call will initialize the memory; uninitialized stack allowed */\nkernel/bpf/verifier.c:6637:\tbool raw_mode = meta \u0026\u0026 meta-\u003earg_raw_mem.regno == reg_from_argno(argno);\nkernel/bpf/verifier.c-6638-\n--\nkernel/bpf/verifier.c-6679-\tif (raw_mode) {\nkernel/bpf/verifier.c:6680:\t\tmeta-\u003earg_raw_mem.size = access_size;\nkernel/bpf/verifier.c-6681-\t\treturn 0;\n--\nkernel/bpf/verifier.c=8277=static int check_func_arg(struct bpf_verifier_env *env, u32 arg,\n--\nkernel/bpf/verifier.c-8403-\t\t    meta-\u003emap.ptr-\u003emap_type == BPF_MAP_TYPE_BLOOM_FILTER)\nkernel/bpf/verifier.c:8404:\t\t\tmeta-\u003earg_raw_mem.regno = 0;\nkernel/bpf/verifier.c-8405-\n--\nkernel/bpf/verifier.c=8789=static bool check_raw_mode_ok(const struct bpf_func_proto *fn, struct bpf_call_arg_meta *meta)\n--\nkernel/bpf/verifier.c-8797-\t\t\tcontinue;\nkernel/bpf/verifier.c:8798:\t\tif (meta-\u003earg_raw_mem.regno)\nkernel/bpf/verifier.c-8799-\t\t\treturn false;\nkernel/bpf/verifier.c:8800:\t\tmeta-\u003earg_raw_mem.regno = i + 1;\nkernel/bpf/verifier.c-8801-\t}\n--\nkernel/bpf/verifier.c=10210=static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,\n--\nkernel/bpf/verifier.c-10295-\t */\nkernel/bpf/verifier.c:10296:\tfor (i = 0; i \u003c meta.arg_raw_mem.size; i++) {\nkernel/bpf/verifier.c:10297:\t\terr = check_mem_access(env, insn_idx, regs + meta.arg_raw_mem.regno,\nkernel/bpf/verifier.c:10298:\t\t\t\t       argno_from_reg(meta.arg_raw_mem.regno), i, BPF_B,\nkernel/bpf/verifier.c-10299-\t\t\t\t       BPF_WRITE, -1, false, false);\n"
}

75/3 2026/08/11 11:34 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45989
							output: 330
							thoughts: 3761
						

Thoughts:
**Uninitialized Memory Leak in Newly Introduced BPF Kfuncs**

Okay, so I've been digging into these new BPF kfuncs, specifically `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`, and I think there's a serious uninitialized memory issue here. It looks like the way the verifier handles stack initialization in conjunction with these kfuncs, especially `bpf_copy_from_user_mm_str`, creates a perfect storm for information leaks, *especially* when `BPF_F_PAD_ZEROS` is *not* used.

Here's the breakdown: When a BPF program calls `bpf_copy_from_user_mm_str`, the verifier, for privileged users (where `allow_uninit_stack` is true), marks the *entire* destination buffer as initialized. However, the `bpf_copy_from_user_mm_str` kfunc doesn't always fully initialize the buffer; it only initializes up to the length of the string copied from userspace, which is null terminated, so it doesn't need to write the entire size. If the `BPF_F_PAD_ZEROS` flag isn't set, then the remaining part of the buffer is left uninitialized. If the BPF program then *reads* from this uninitialized portion, it's reading kernel stack memory that hasn't been written to.

KASAN won't catch this because the read is within the bounds of the stack. But KMSAN, which is designed to identify exactly this type of uninitialized memory read, *will* detect it. The kfunc *copy_remote_mm_str* returns the number of bytes copied. The verifier marks the buffer size dst_sz as initialized, but the code does not initialize the bytes beyond return value from *copy_remote_mm_str*, if not using `BPF_F_PAD_ZEROS`.

Now, for `bpf_copy_from_user_mm`, the function either fully writes to the buffer, or it zeros it if there's an error. Therefore, there are no uninitialized memory concerns here.

Since the test case uses an LSM program, it requires privileges, and this means `allow_uninit_stack` will always be true when these kfuncs are in use, making the leak possible. Moreover, tracing programs (kprobe, tracepoint, perf_event, etc.) require CAP_PERFMON which defaults to `allow_uninit_stack` being set to true.

Therefore, KMSAN is absolutely crucial here. We need a dedicated KMSAN fuzzing session to find and expose this information leak, which the verifier lets happen due to insufficient tracking of memory initialization.



76/3 2026/08/11 11:34 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch introduces two new BPF kfuncs: `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`, which copy data from a user address space into a kernel buffer (`dst`). These kfuncs are available to privileged BPF programs (e.g., tracing or LSM programs). For privileged programs, the BPF verifier allows passing uninitialized kernel stack memory to kfuncs and assumes the kfunc will fully initialize the buffer (marking the entire buffer as initialized in the verifier state).\n\nHowever, `bpf_copy_from_user_mm_str` only fully initializes the `dst` buffer if the `BPF_F_PAD_ZEROS` flag is provided. If this flag is omitted, the function only initializes the buffer up to the length of the copied string (plus the NUL terminator), leaving the remainder of the buffer uninitialized. Because the verifier incorrectly marks the entire buffer as initialized, a BPF program can read the uninitialized tail of the buffer and leak kernel stack memory to userspace (e.g., via BPF maps).\n\nStandard KASAN cannot detect this issue because the memory accesses are strictly within the bounds of the allocated stack buffer. KMSAN is specifically designed to track uninitialized memory at runtime and will reliably detect this kernel info-leak when the BPF program reads the uninitialized bytes. Therefore, a dedicated KMSAN fuzzing session is highly justified.",
  "NeedsKMSAN": true
}

Results:
{
  "KMSANReasoning": "The patch introduces two new BPF kfuncs: `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`, which copy data from a user address space into a kernel buffer (`dst`). These kfuncs are available to privileged BPF programs (e.g., tracing or LSM programs). For privileged programs, the BPF verifier allows passing uninitialized kernel stack memory to kfuncs and assumes the kfunc will fully initialize the buffer (marking the entire buffer as initialized in the verifier state).\n\nHowever, `bpf_copy_from_user_mm_str` only fully initializes the `dst` buffer if the `BPF_F_PAD_ZEROS` flag is provided. If this flag is omitted, the function only initializes the buffer up to the length of the copied string (plus the NUL terminator), leaving the remainder of the buffer uninitialized. Because the verifier incorrectly marks the entire buffer as initialized, a BPF program can read the uninitialized tail of the buffer and leak kernel stack memory to userspace (e.g., via BPF maps).\n\nStandard KASAN cannot detect this issue because the memory accesses are strictly within the bounds of the allocated stack buffer. KMSAN is specifically designed to track uninitialized memory at runtime and will reliably detect this kernel info-leak when the BPF program reads the uninitialized bytes. Therefore, a dedicated KMSAN fuzzing session is highly justified.",
  "NeedsKMSAN": true
}

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)