AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/08 17:14 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "bpf_copy_from_user_task",
    "bpf_copy_from_user_task_str",
    "copy_remote_mm_str",
    "copy_remote_vm_str",
    "__copy_remote_mm_str"
  ],
  "KMSANReasoning": "The patch introduces new BPF kfuncs (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`) and refactors existing helpers (`bpf_copy_from_user_task` and `bpf_copy_from_user_task_str`) along with `copy_remote_vm_str`/`copy_remote_mm_str` helpers to read user memory from an `mm_struct`. It also sets `bprm-\u003emm = NULL` in `free_bprm()`.\n\nAll destination buffers are written either directly via `access_remote_vm`/`copy_remote_mm_str` or explicitly cleared with `memset` on error and padding conditions. No new uninitialized stack, heap, or page allocations are introduced or passed across trust boundaries without initialization. Potential bugs introduced by these changes (such as lifetime issues, NULL pointer dereferences, refcounting issues, or out-of-bounds accesses) are covered by KASAN and standard kernel debug tooling. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm, bpf_copy_from_user_mm_str), refactors existing helpers and kfuncs (bpf_copy_from_user_task, bpf_copy_from_user_task_str), adds copy_remote_mm_str / __copy_remote_mm_str for reading strings from remote mm_struct instances, and allows trusted access to bprm-\u003emm in the BPF verifier. These changes touch core reachable BPF and MM logic and warrant fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/09/08 17:14 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit a94c69a754610027e65484417ca4ae54dcd4f740\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 8 17:14:54 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/exec.c b/fs/exec.c\nindex 745f6eb5279e6..4ddd403fd91c7 100644\n--- a/fs/exec.c\n+++ b/fs/exec.c\n@@ -1456,9 +1456,12 @@ void bprm_drop_loader(struct linux_binprm *bprm)\n \n static void free_bprm(struct linux_binprm *bprm)\n {\n-\tif (bprm-\u003emm) {\n+\tstruct mm_struct *mm = bprm-\u003emm;\n+\n+\tif (mm) {\n \t\tacct_arg_size(bprm, 0);\n-\t\tmmput(bprm-\u003emm);\n+\t\tbprm-\u003emm = NULL;\n+\t\tmmput(mm);\n \t}\n \tif (bprm-\u003euser_ns)\n \t\tput_user_ns(bprm-\u003euser_ns);\ndiff --git a/include/linux/mm.h b/include/linux/mm.h\nindex dd09c438fa23e..6f10ce315eaa8 100644\n--- a/include/linux/mm.h\n+++ b/include/linux/mm.h\n@@ -3325,10 +3325,10 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,\n 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_vm_str(struct task_struct *tsk, unsigned long addr,\n-\t\t\t      void *buf, int len, unsigned int gup_flags);\n-#endif\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+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n+\t\t       void *buf, int len, unsigned int gup_flags);\n \n long get_user_pages_remote(struct mm_struct *mm,\n \t\t\t   unsigned long start, unsigned long nr_pages,\ndiff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c\nindex b3cc5c8fc8756..3338bebdd86e7 100644\n--- a/kernel/bpf/helpers.c\n+++ b/kernel/bpf/helpers.c\n@@ -679,9 +679,44 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {\n \t.arg3_type\t= ARG_ANYTHING,\n };\n \n+static int __bpf_copy_from_user_mm(void *dst, u32 size,\n+\t\t\t\t   const void __user *user_ptr,\n+\t\t\t\t   struct mm_struct *mm)\n+{\n+\tint ret;\n+\n+\tret = access_remote_vm(mm, (unsigned long)user_ptr, dst, size, 0);\n+\tif (ret == size)\n+\t\treturn 0;\n+\n+\tmemset(dst, 0, size);\n+\t/* Return -EFAULT for partial read */\n+\treturn ret \u003c 0 ? ret : -EFAULT;\n+}\n+\n+static int __bpf_copy_from_user_mm_str(void *dst, u32 size,\n+\t\t\t\t       const void __user *user_ptr,\n+\t\t\t\t       struct mm_struct *mm, u64 flags)\n+{\n+\tint ret;\n+\n+\tret = copy_remote_mm_str(mm, (unsigned long)user_ptr, dst, size, 0);\n+\tif (ret \u003c 0) {\n+\t\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\t\tmemset(dst, 0, size);\n+\t\treturn ret;\n+\t}\n+\n+\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\tmemset(dst + ret, 0, size - ret);\n+\n+\treturn ret + 1;\n+}\n+\n BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,\n \t   const void __user *, user_ptr, struct task_struct *, tsk, u64, flags)\n {\n+\tstruct mm_struct *mm;\n \tint ret;\n \n \t/* flags is not used yet */\n@@ -691,13 +726,16 @@ BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,\n \tif (unlikely(!size))\n \t\treturn 0;\n \n-\tret = access_process_vm(tsk, (unsigned long)user_ptr, dst, size, 0);\n-\tif (ret == size)\n-\t\treturn 0;\n+\tmm = get_task_mm(tsk);\n+\tif (!mm) {\n+\t\tmemset(dst, 0, size);\n+\t\treturn -EFAULT;\n+\t}\n \n-\tmemset(dst, 0, size);\n-\t/* Return -EFAULT for partial read */\n-\treturn ret \u003c 0 ? ret : -EFAULT;\n+\tret = __bpf_copy_from_user_mm(dst, size, user_ptr, mm);\n+\tmmput(mm);\n+\n+\treturn ret;\n }\n \n const struct bpf_func_proto bpf_copy_from_user_task_proto = {\n@@ -3658,6 +3696,68 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user\n \treturn ret + 1;\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 an attempted copy cannot be completed in full. Unsupported\n+ * flags return -EINVAL without modifying @dst.\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+\tif (unlikely(flags))\n+\t\treturn -EINVAL;\n+\n+\tif (unlikely(!dst__sz))\n+\t\treturn 0;\n+\n+\treturn __bpf_copy_from_user_mm(dst, dst__sz, unsafe_ptr__ign, mm);\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 the flags are valid and BPF_F_PAD_ZEROS is set, the unused portion of\n+ * @dst is cleared on success and all of @dst is cleared on a copy failure.\n+ * Unsupported flags return -EINVAL without modifying @dst.\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+\tif (unlikely(flags \u0026 ~BPF_F_PAD_ZEROS))\n+\t\treturn -EINVAL;\n+\n+\tif (unlikely(dst__sz == 0))\n+\t\treturn 0;\n+\n+\treturn __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,\n+\t\t\t\t\t   mm, flags);\n+}\n+\n /**\n  * bpf_copy_from_user_task_str() - Copy a string from an task's address space\n  * @dst:             Destination address, in kernel space.  This buffer must be\n@@ -3681,6 +3781,7 @@ __bpf_kfunc int bpf_copy_from_user_task_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 task_struct *tsk, u64 flags)\n {\n+\tstruct mm_struct *mm;\n \tint ret;\n \n \tif (unlikely(flags \u0026 ~BPF_F_PAD_ZEROS))\n@@ -3689,17 +3790,20 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,\n \tif (unlikely(dst__sz == 0))\n \t\treturn 0;\n \n-\tret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);\n-\tif (ret \u003c 0) {\n+\tmm = get_task_mm(tsk);\n+\tif (!mm) {\n \t\tif (flags \u0026 BPF_F_PAD_ZEROS)\n \t\t\tmemset(dst, 0, dst__sz);\n-\t\treturn ret;\n+\t\telse\n+\t\t\t*(char *)dst = '\\0';\n+\t\treturn -EFAULT;\n \t}\n \n-\tif (flags \u0026 BPF_F_PAD_ZEROS)\n-\t\tmemset(dst + ret, 0, dst__sz - ret);\n+\tret = __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,\n+\t\t\t\t\t  mm, flags);\n+\tmmput(mm);\n \n-\treturn ret + 1;\n+\treturn ret;\n }\n \n /* Keep unsigned long in prototype so that kfunc is usable when emitted to\n@@ -4924,6 +5028,8 @@ BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)\n BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)\n BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)\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_copy_from_user_task_str, KF_SLEEPABLE)\n BTF_ID_FLAGS(func, bpf_get_kmem_cache)\n BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex 9e79750e24808..791b3d25caa54 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -6004,6 +6004,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@@ -6058,6 +6062,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/mm/internal.h b/mm/internal.h\nindex 38b1165212c94..557b293813557 100644\n--- a/mm/internal.h\n+++ b/mm/internal.h\n@@ -25,6 +25,9 @@\n struct folio_batch;\n struct hstate;\n \n+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+\n struct huge_bootmem_page {\n \tstruct list_head list;\n \tstruct hstate *hstate;\ndiff --git a/mm/memory.c b/mm/memory.c\nindex 8b0c2c735d3de..fe2f5e988fb90 100644\n--- a/mm/memory.c\n+++ b/mm/memory.c\n@@ -7331,8 +7331,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);\n  * Copy a string from another process's address space as given in mm.\n  * If there is any error return -EFAULT.\n  */\n-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n-\t\t\t\tvoid *buf, int len, unsigned int gup_flags)\n+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 {\n \tvoid *old_buf = buf;\n \tint err = 0;\n@@ -7407,43 +7407,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n \t\treturn err;\n \treturn buf - old_buf;\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- * @addr:\tstart address to read from\n- * @buf:\tdestination buffer\n- * @len:\tnumber of bytes to copy\n- * @gup_flags:\tflags 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 guaranteed to leave NUL-terminated\n- * buffer. On any error, return -EFAULT.\n- */\n-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n-\t\t       void *buf, int len, unsigned int gup_flags)\n-{\n-\tstruct mm_struct *mm;\n-\tint ret;\n-\n-\tif (unlikely(len == 0))\n-\t\treturn 0;\n-\n-\tmm = get_task_mm(tsk);\n-\tif (!mm) {\n-\t\t*(char *)buf = '\\0';\n-\t\treturn -EFAULT;\n-\t}\n-\n-\tret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);\n-\n-\tmmput(mm);\n-\n-\treturn ret;\n-}\n-EXPORT_SYMBOL_GPL(copy_remote_vm_str);\n #endif /* CONFIG_BPF_SYSCALL */\n \n /*\ndiff --git a/mm/nommu.c b/mm/nommu.c\nindex 498e01ee40b05..98596e60311f3 100644\n--- a/mm/nommu.c\n+++ b/mm/nommu.c\n@@ -1746,8 +1746,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);\n  * Copy a string from another process's address space as given in mm.\n  * If there is any error return -EFAULT.\n  */\n-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n-\t\t\t\tvoid *buf, int len)\n+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 {\n \tunsigned long addr_end;\n \tstruct vm_area_struct *vma;\n@@ -1781,43 +1781,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,\n \tmmap_read_unlock(mm);\n \treturn ret;\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- * @addr:\tstart address to read from\n- * @buf:\tdestination buffer\n- * @len:\tnumber of bytes to copy\n- * @gup_flags:\tflags 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 guaranteed to leave NUL-terminated\n- * buffer. On any error, return -EFAULT.\n- */\n-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n-\t\t       void *buf, int len, unsigned int gup_flags)\n-{\n-\tstruct mm_struct *mm;\n-\tint ret;\n-\n-\tif (unlikely(len == 0))\n-\t\treturn 0;\n-\n-\tmm = get_task_mm(tsk);\n-\tif (!mm) {\n-\t\t*(char *)buf = '\\0';\n-\t\treturn -EFAULT;\n-\t}\n-\n-\tret = __copy_remote_vm_str(mm, addr, buf, len);\n-\n-\tmmput(mm);\n-\n-\treturn ret;\n-}\n-EXPORT_SYMBOL_GPL(copy_remote_vm_str);\n #endif /* CONFIG_BPF_SYSCALL */\n \n /**\ndiff --git a/mm/util.c b/mm/util.c\nindex bf0513d1d3d08..2eca27b02791f 100644\n--- a/mm/util.c\n+++ b/mm/util.c\n@@ -1061,6 +1061,68 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen)\n \treturn res;\n }\n \n+#ifdef CONFIG_BPF_SYSCALL\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. If @len is zero, return 0 without accessing\n+ * @buf. Otherwise, @buf is always NUL-terminated. On any error, return\n+ * -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_mm_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+ * @addr:\tstart address to read from\n+ * @buf:\tdestination buffer\n+ * @len:\tnumber of bytes to copy\n+ * @gup_flags:\tflags modifying lookup behaviour\n+ *\n+ * Return: number of bytes copied from @addr (source) to @buf (destination),\n+ * not including the trailing NUL. If @len is zero, return 0 without accessing\n+ * @buf. Otherwise, @buf is always NUL-terminated. On any error, return\n+ * -EFAULT.\n+ */\n+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n+\t\t       void *buf, int len, unsigned int gup_flags)\n+{\n+\tstruct mm_struct *mm;\n+\tint ret;\n+\n+\tif (unlikely(len == 0))\n+\t\treturn 0;\n+\n+\tmm = get_task_mm(tsk);\n+\tif (!mm) {\n+\t\t*(char *)buf = '\\0';\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tret = __copy_remote_mm_str(mm, addr, buf, len, gup_flags);\n+\n+\tmmput(mm);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(copy_remote_vm_str);\n+#endif /* CONFIG_BPF_SYSCALL */\n+\n int __weak memcmp_pages(struct page *page1, struct page *page2)\n {\n \tchar *addr1, *addr2;\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..b2325b1935761\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c\n@@ -0,0 +1,72 @@\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+\tchar arg0[] = \"first\";\n+\tchar arg1[] = \"second-argument\";\n+\tchar env0[] = \"SOME_ENV=a\";\n+\tchar env1[] = \"OTHER_ENV=something\";\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+\t/*\n+\t * On !CONFIG_MMU, exec strings are held in bprm-\u003epage[] rather than\n+\t * being mapped in bprm-\u003emm.\n+\t */\n+\tif (!skel-\u003ekconfig-\u003eCONFIG_MMU) {\n+\t\tprintf(\"%s:SKIP: test requires CONFIG_MMU\\n\", __func__);\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\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[] = { arg0, arg1, NULL };\n+\t\tchar *const envp[] = { env0, env1, NULL };\n+\n+\t\tskel-\u003ebss-\u003emonitored_pid = getpid();\n+\t\texecve(\"/bin/true\", argv, envp);\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-\u003ebprm_envc, 2, \"bprm_envc\");\n+\tASSERT_EQ(skel-\u003ebss-\u003edata_len_match, 1, \"data_len_match\");\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, sizeof(arg0), \"str_arg0_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_arg1_ret, sizeof(arg1), \"str_arg1_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_env0_ret, sizeof(env0), \"str_env0_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_env1_ret, sizeof(env1), \"str_env1_ret\");\n+\tASSERT_EQ(skel-\u003ebss-\u003edata_match, 1, \"data_match\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_args_match, 1, \"str_args_match\");\n+\tASSERT_EQ(skel-\u003ebss-\u003estr_envs_match, 1, \"str_envs_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..b334a157419e5\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c\n@@ -0,0 +1,123 @@\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_data[] = \"first\\0second-argument\\0\"\n+\t\t\t\t    \"SOME_ENV=a\\0OTHER_ENV=something\";\n+static const char expected_arg0[] = \"first\";\n+static const char expected_arg1[] = \"second-argument\";\n+static const char expected_env0[] = \"SOME_ENV=a\";\n+static const char expected_env1[] = \"OTHER_ENV=something\";\n+\n+int monitored_pid;\n+int bprm_argc;\n+int bprm_envc;\n+int data_len_match;\n+int invalid_flags_ret;\n+int copy_ret;\n+int str_arg0_ret;\n+int str_arg1_ret;\n+int str_env0_ret;\n+int str_env1_ret;\n+int data_match;\n+int str_args_match;\n+int str_envs_match;\n+\n+extern bool CONFIG_MMU __kconfig __weak;\n+\n+extern int bpf_copy_from_user_mm(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+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)\n+{\n+\tu32 pid = bpf_get_current_pid_tgid() \u003e\u003e 32;\n+\tchar data[sizeof(expected_data)] = {};\n+\tstruct mm_struct *mm;\n+\tchar arg0[32] = {};\n+\tchar arg1[32] = {};\n+\tchar env0[32] = {};\n+\tchar env1[32] = {};\n+\tu64 offset = 0;\n+\tu64 data_len;\n+\n+\tif (!CONFIG_MMU)\n+\t\treturn 0;\n+\n+\tif (pid != monitored_pid)\n+\t\treturn 0;\n+\n+\tmm = bprm-\u003emm;\n+\tif (!mm)\n+\t\treturn 0;\n+\n+\tbprm_argc = bprm-\u003eargc;\n+\tbprm_envc = bprm-\u003eenvc;\n+\n+\t/* this is the total size of args and envs starting from bprm-\u003ep */\n+\tdata_len = bprm-\u003eexec - bprm-\u003ep;\n+\tdata_len_match = data_len == sizeof(expected_data);\n+\n+\tinvalid_flags_ret = bpf_copy_from_user_mm(data,\n+\t\t\t\t\t\t  sizeof(data), (void *)bprm-\u003ep, mm, ~0ULL);\n+\n+\tcopy_ret = bpf_copy_from_user_mm(data, sizeof(data), (void *)bprm-\u003ep,\n+\t\t\t\t\t mm, 0);\n+\tif (copy_ret)\n+\t\treturn 0;\n+\n+\tdata_match =\n+\t\t!__builtin_memcmp(data, expected_data, sizeof(expected_data));\n+\n+\t/* arg0 is at bprm-\u003ep */\n+\tstr_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),\n+\t\t\t\t\t\t (void *)(bprm-\u003ep + offset),\n+\t\t\t\t\t\t mm, BPF_F_PAD_ZEROS);\n+\tif (str_arg0_ret != sizeof(expected_arg0))\n+\t\treturn 0;\n+\toffset += str_arg0_ret;\n+\n+\t/* arg1 is at bprm-\u003ep + sizeof(arg0) */\n+\tstr_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),\n+\t\t\t\t\t\t (void *)(bprm-\u003ep + offset),\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+\toffset += str_arg1_ret;\n+\n+\t/* env0 is at bprm-\u003ep + sizeof(arg0) + sizeof(arg1) */\n+\tstr_env0_ret = bpf_copy_from_user_mm_str(env0, sizeof(env0),\n+\t\t\t\t\t\t (void *)(bprm-\u003ep + offset),\n+\t\t\t\t\t\t mm, BPF_F_PAD_ZEROS);\n+\tif (str_env0_ret != sizeof(expected_env0))\n+\t\treturn 0;\n+\toffset += str_env0_ret;\n+\n+\t/* env1 is at bprm-\u003ep + sizeof(arg0) + sizeof(arg1) + sizeof(env0) */\n+\tstr_env1_ret = bpf_copy_from_user_mm_str(env1, sizeof(env1),\n+\t\t\t\t\t\t (void *)(bprm-\u003ep + offset),\n+\t\t\t\t\t\t mm, BPF_F_PAD_ZEROS);\n+\tif (str_env1_ret != sizeof(expected_env1))\n+\t\treturn 0;\n+\n+\tstr_args_match =\n+\t\t!__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) \u0026\u0026\n+\t\t!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));\n+\tstr_envs_match =\n+\t\t!__builtin_memcmp(env0, expected_env0, sizeof(expected_env0)) \u0026\u0026\n+\t\t!__builtin_memcmp(env1, expected_env1, sizeof(expected_env1));\n+\n+\treturn data_match \u0026\u0026 str_args_match \u0026\u0026 str_envs_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)\n"
}

2/1 2026/09/08 17:14 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "bpf_copy_from_user_task",
    "bpf_copy_from_user_task_str",
    "copy_remote_mm_str",
    "copy_remote_vm_str",
    "__copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm, bpf_copy_from_user_mm_str), refactors existing helpers and kfuncs (bpf_copy_from_user_task, bpf_copy_from_user_task_str), adds copy_remote_mm_str / __copy_remote_mm_str for reading strings from remote mm_struct instances, and allows trusted access to bprm-\u003emm in the BPF verifier. These changes touch core reachable BPF and MM logic and warrant fuzzing.",
  "WorthFuzzing": true
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit a94c69a754610027e65484417ca4ae54dcd4f740
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 17:14:54 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/exec.c b/fs/exec.c
index 745f6eb5279e6..4ddd403fd91c7 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1456,9 +1456,12 @@ void bprm_drop_loader(struct linux_binprm *bprm)
 
 static void free_bprm(struct linux_binprm *bprm)
 {
-	if (bprm->mm) {
+	struct mm_struct *mm = bprm->mm;
+
+	if (mm) {
 		acct_arg_size(bprm, 0);
-		mmput(bprm->mm);
+		bprm->mm = NULL;
+		mmput(mm);
 	}
 	if (bprm->user_ns)
 		put_user_ns(bprm->user_ns);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23e..6f10ce315eaa8 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3325,10 +3325,10 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
 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_vm_str(struct task_struct *tsk, unsigned long addr,
-			      void *buf, int len, unsigned int gup_flags);
-#endif
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags);
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags);
 
 long get_user_pages_remote(struct mm_struct *mm,
 			   unsigned long start, unsigned long nr_pages,
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..3338bebdd86e7 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -679,9 +679,44 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
 	.arg3_type	= ARG_ANYTHING,
 };
 
+static int __bpf_copy_from_user_mm(void *dst, u32 size,
+				   const void __user *user_ptr,
+				   struct mm_struct *mm)
+{
+	int ret;
+
+	ret = access_remote_vm(mm, (unsigned long)user_ptr, dst, size, 0);
+	if (ret == size)
+		return 0;
+
+	memset(dst, 0, size);
+	/* Return -EFAULT for partial read */
+	return ret < 0 ? ret : -EFAULT;
+}
+
+static int __bpf_copy_from_user_mm_str(void *dst, u32 size,
+				       const void __user *user_ptr,
+				       struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	ret = copy_remote_mm_str(mm, (unsigned long)user_ptr, dst, size, 0);
+	if (ret < 0) {
+		if (flags & BPF_F_PAD_ZEROS)
+			memset(dst, 0, size);
+		return ret;
+	}
+
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst + ret, 0, size - ret);
+
+	return ret + 1;
+}
+
 BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
 	   const void __user *, user_ptr, struct task_struct *, tsk, u64, flags)
 {
+	struct mm_struct *mm;
 	int ret;
 
 	/* flags is not used yet */
@@ -691,13 +726,16 @@ BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
 	if (unlikely(!size))
 		return 0;
 
-	ret = access_process_vm(tsk, (unsigned long)user_ptr, dst, size, 0);
-	if (ret == size)
-		return 0;
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		memset(dst, 0, size);
+		return -EFAULT;
+	}
 
-	memset(dst, 0, size);
-	/* Return -EFAULT for partial read */
-	return ret < 0 ? ret : -EFAULT;
+	ret = __bpf_copy_from_user_mm(dst, size, user_ptr, mm);
+	mmput(mm);
+
+	return ret;
 }
 
 const struct bpf_func_proto bpf_copy_from_user_task_proto = {
@@ -3658,6 +3696,68 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user
 	return ret + 1;
 }
 
+/**
+ * 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 an attempted copy cannot be completed in full. Unsupported
+ * flags return -EINVAL without modifying @dst.
+ *
+ * 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)
+{
+	if (unlikely(flags))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	return __bpf_copy_from_user_mm(dst, dst__sz, unsafe_ptr__ign, mm);
+}
+
+/**
+ * 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 the flags are valid and BPF_F_PAD_ZEROS is set, the unused portion of
+ * @dst is cleared on success and all of @dst is cleared on a copy failure.
+ * Unsupported flags return -EINVAL without modifying @dst.
+ *
+ * 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)
+{
+	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+		return -EINVAL;
+
+	if (unlikely(dst__sz == 0))
+		return 0;
+
+	return __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+					   mm, flags);
+}
+
 /**
  * bpf_copy_from_user_task_str() - Copy a string from an task's address space
  * @dst:             Destination address, in kernel space.  This buffer must be
@@ -3681,6 +3781,7 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
 					    const void __user *unsafe_ptr__ign,
 					    struct task_struct *tsk, u64 flags)
 {
+	struct mm_struct *mm;
 	int ret;
 
 	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
@@ -3689,17 +3790,20 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
 	if (unlikely(dst__sz == 0))
 		return 0;
 
-	ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
-	if (ret < 0) {
+	mm = get_task_mm(tsk);
+	if (!mm) {
 		if (flags & BPF_F_PAD_ZEROS)
 			memset(dst, 0, dst__sz);
-		return ret;
+		else
+			*(char *)dst = '\0';
+		return -EFAULT;
 	}
 
-	if (flags & BPF_F_PAD_ZEROS)
-		memset(dst + ret, 0, dst__sz - ret);
+	ret = __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+					  mm, flags);
+	mmput(mm);
 
-	return ret + 1;
+	return ret;
 }
 
 /* Keep unsigned long in prototype so that kfunc is usable when emitted to
@@ -4924,6 +5028,8 @@ BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)
 BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)
+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_copy_from_user_task_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_get_kmem_cache)
 BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e79750e24808..791b3d25caa54 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6004,6 +6004,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;
 };
@@ -6058,6 +6062,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/mm/internal.h b/mm/internal.h
index 38b1165212c94..557b293813557 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -25,6 +25,9 @@
 struct folio_batch;
 struct hstate;
 
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags);
+
 struct huge_bootmem_page {
 	struct list_head list;
 	struct hstate *hstate;
diff --git a/mm/memory.c b/mm/memory.c
index 8b0c2c735d3de..fe2f5e988fb90 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7331,8 +7331,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);
  * Copy a string from another process's address space as given in mm.
  * If there is any error return -EFAULT.
  */
-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
-				void *buf, int len, unsigned int gup_flags)
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags)
 {
 	void *old_buf = buf;
 	int err = 0;
@@ -7407,43 +7407,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 		return err;
 	return buf - old_buf;
 }
-
-/**
- * copy_remote_vm_str - copy a string from another process's address space.
- * @tsk:	the task of the target 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 guaranteed to leave NUL-terminated
- * buffer. On any error, return -EFAULT.
- */
-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
-		       void *buf, int len, unsigned int gup_flags)
-{
-	struct mm_struct *mm;
-	int ret;
-
-	if (unlikely(len == 0))
-		return 0;
-
-	mm = get_task_mm(tsk);
-	if (!mm) {
-		*(char *)buf = '\0';
-		return -EFAULT;
-	}
-
-	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
-
-	mmput(mm);
-
-	return ret;
-}
-EXPORT_SYMBOL_GPL(copy_remote_vm_str);
 #endif /* CONFIG_BPF_SYSCALL */
 
 /*
diff --git a/mm/nommu.c b/mm/nommu.c
index 498e01ee40b05..98596e60311f3 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1746,8 +1746,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);
  * Copy a string from another process's address space as given in mm.
  * If there is any error return -EFAULT.
  */
-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
-				void *buf, int len)
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags)
 {
 	unsigned long addr_end;
 	struct vm_area_struct *vma;
@@ -1781,43 +1781,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	mmap_read_unlock(mm);
 	return ret;
 }
-
-/**
- * copy_remote_vm_str - copy a string from another process's address space.
- * @tsk:	the task of the target 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 guaranteed to leave NUL-terminated
- * buffer. On any error, return -EFAULT.
- */
-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
-		       void *buf, int len, unsigned int gup_flags)
-{
-	struct mm_struct *mm;
-	int ret;
-
-	if (unlikely(len == 0))
-		return 0;
-
-	mm = get_task_mm(tsk);
-	if (!mm) {
-		*(char *)buf = '\0';
-		return -EFAULT;
-	}
-
-	ret = __copy_remote_vm_str(mm, addr, buf, len);
-
-	mmput(mm);
-
-	return ret;
-}
-EXPORT_SYMBOL_GPL(copy_remote_vm_str);
 #endif /* CONFIG_BPF_SYSCALL */
 
 /**
diff --git a/mm/util.c b/mm/util.c
index bf0513d1d3d08..2eca27b02791f 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1061,6 +1061,68 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen)
 	return res;
 }
 
+#ifdef CONFIG_BPF_SYSCALL
+/**
+ * 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. If @len is zero, return 0 without accessing
+ * @buf. Otherwise, @buf is always NUL-terminated. 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_mm_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
+ * @addr:	start address to read from
+ * @buf:	destination buffer
+ * @len:	number of bytes to copy
+ * @gup_flags:	flags modifying lookup behaviour
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. If @len is zero, return 0 without accessing
+ * @buf. Otherwise, @buf is always NUL-terminated. On any error, return
+ * -EFAULT.
+ */
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	struct mm_struct *mm;
+	int ret;
+
+	if (unlikely(len == 0))
+		return 0;
+
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		*(char *)buf = '\0';
+		return -EFAULT;
+	}
+
+	ret = __copy_remote_mm_str(mm, addr, buf, len, gup_flags);
+
+	mmput(mm);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(copy_remote_vm_str);
+#endif /* CONFIG_BPF_SYSCALL */
+
 int __weak memcmp_pages(struct page *page1, struct page *page2)
 {
 	char *addr1, *addr2;
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..b2325b1935761
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
@@ -0,0 +1,72 @@
+// 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)
+{
+	char arg0[] = "first";
+	char arg1[] = "second-argument";
+	char env0[] = "SOME_ENV=a";
+	char env1[] = "OTHER_ENV=something";
+	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;
+
+	/*
+	 * On !CONFIG_MMU, exec strings are held in bprm->page[] rather than
+	 * being mapped in bprm->mm.
+	 */
+	if (!skel->kconfig->CONFIG_MMU) {
+		printf("%s:SKIP: test requires CONFIG_MMU\n", __func__);
+		test__skip();
+		goto out;
+	}
+
+	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[] = { arg0, arg1, NULL };
+		char *const envp[] = { env0, env1, NULL };
+
+		skel->bss->monitored_pid = getpid();
+		execve("/bin/true", argv, envp);
+		_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->bprm_envc, 2, "bprm_envc");
+	ASSERT_EQ(skel->bss->data_len_match, 1, "data_len_match");
+	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, sizeof(arg0), "str_arg0_ret");
+	ASSERT_EQ(skel->bss->str_arg1_ret, sizeof(arg1), "str_arg1_ret");
+	ASSERT_EQ(skel->bss->str_env0_ret, sizeof(env0), "str_env0_ret");
+	ASSERT_EQ(skel->bss->str_env1_ret, sizeof(env1), "str_env1_ret");
+	ASSERT_EQ(skel->bss->data_match, 1, "data_match");
+	ASSERT_EQ(skel->bss->str_args_match, 1, "str_args_match");
+	ASSERT_EQ(skel->bss->str_envs_match, 1, "str_envs_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..b334a157419e5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
@@ -0,0 +1,123 @@
+// 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_data[] = "first\0second-argument\0"
+				    "SOME_ENV=a\0OTHER_ENV=something";
+static const char expected_arg0[] = "first";
+static const char expected_arg1[] = "second-argument";
+static const char expected_env0[] = "SOME_ENV=a";
+static const char expected_env1[] = "OTHER_ENV=something";
+
+int monitored_pid;
+int bprm_argc;
+int bprm_envc;
+int data_len_match;
+int invalid_flags_ret;
+int copy_ret;
+int str_arg0_ret;
+int str_arg1_ret;
+int str_env0_ret;
+int str_env1_ret;
+int data_match;
+int str_args_match;
+int str_envs_match;
+
+extern bool CONFIG_MMU __kconfig __weak;
+
+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)
+{
+	u32 pid = bpf_get_current_pid_tgid() >> 32;
+	char data[sizeof(expected_data)] = {};
+	struct mm_struct *mm;
+	char arg0[32] = {};
+	char arg1[32] = {};
+	char env0[32] = {};
+	char env1[32] = {};
+	u64 offset = 0;
+	u64 data_len;
+
+	if (!CONFIG_MMU)
+		return 0;
+
+	if (pid != monitored_pid)
+		return 0;
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bprm_argc = bprm->argc;
+	bprm_envc = bprm->envc;
+
+	/* this is the total size of args and envs starting from bprm->p */
+	data_len = bprm->exec - bprm->p;
+	data_len_match = data_len == sizeof(expected_data);
+
+	invalid_flags_ret = bpf_copy_from_user_mm(data,
+						  sizeof(data), (void *)bprm->p, mm, ~0ULL);
+
+	copy_ret = bpf_copy_from_user_mm(data, sizeof(data), (void *)bprm->p,
+					 mm, 0);
+	if (copy_ret)
+		return 0;
+
+	data_match =
+		!__builtin_memcmp(data, expected_data, sizeof(expected_data));
+
+	/* arg0 is at bprm->p */
+	str_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg0_ret != sizeof(expected_arg0))
+		return 0;
+	offset += str_arg0_ret;
+
+	/* arg1 is at bprm->p + sizeof(arg0) */
+	str_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg1_ret != sizeof(expected_arg1))
+		return 0;
+	offset += str_arg1_ret;
+
+	/* env0 is at bprm->p + sizeof(arg0) + sizeof(arg1) */
+	str_env0_ret = bpf_copy_from_user_mm_str(env0, sizeof(env0),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_env0_ret != sizeof(expected_env0))
+		return 0;
+	offset += str_env0_ret;
+
+	/* env1 is at bprm->p + sizeof(arg0) + sizeof(arg1) + sizeof(env0) */
+	str_env1_ret = bpf_copy_from_user_mm_str(env1, sizeof(env1),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_env1_ret != sizeof(expected_env1))
+		return 0;
+
+	str_args_match =
+		!__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) &&
+		!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));
+	str_envs_match =
+		!__builtin_memcmp(env0, expected_env0, sizeof(expected_env0)) &&
+		!__builtin_memcmp(env1, expected_env1, sizeof(expected_env1));
+
+	return data_match && str_args_match && str_envs_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)

3/2 2026/09/08 17:14 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 11522
							output: 30
							thoughts: 803
						

Thoughts:
**Patch Analysis and Fuzzing Strategy**

Okay, let's dive into this patch. My initial assessment is that it looks promising for fuzzing, given that it touches some critical areas of the kernel – BPF, memory management, and the execution subsystem (VFS). We're talking about BPF kfuncs, memory copying from user space, and modifications to `free_bprm` which is a critical point of execution and memory management. This is definitely worth a closer look.

Specifically, I see changes to `fs/exec.c` with the `free_bprm` function, the addition of new and modified BPF kfuncs like `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, and updates to `bpf_copy_from_user_task` and `bpf_copy_from_user_task_str`. These BPF interactions, especially those involving user-space memory copies, are often goldmines for fuzzing due to the potential for memory corruption or information leaks if the kernel side doesn't correctly handle invalid pointers or boundary checks. I also see that the verifier is updated, which could also be interesting.

The diffs related to the `mm` subsystem, with `copy_remote_mm_str`, `copy_remote_vm_str`, and the internal renaming, are another key area. Any time there are changes to how the kernel interacts with user-space memory, especially when copying strings, I get interested. The interaction of all these components makes this patch even more enticing.

Given that Syzkaller already regularly fuzzes BPF, the verifier, and the `execve` system call, this patch slots right into existing fuzzing strategies. Definitely reachable. **WorthFuzzing = True**.

Now, it's time to meticulously extract the focus symbols and check the relevant configurations. We will need to make sure we're hitting the right code paths. I need to use grepper or read-file to carefully go through the Kconfigs and ensure that the right options are enabled. The devil is in the details, especially when dealing with such foundational pieces of the kernel.



4/2 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "BPF_SYSCALL",
  "PathPrefix": "kernel/bpf"
}

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

5/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 15688
							output: 34
							thoughts: 134
						

Thoughts:
**My BPF Kfunc and Helper Investigation**

Okay, so I need to dig into how `bpf_copy_from_user_mm` and its string variant, along with the broader kfuncs within BPF, are configured. My immediate thought is to check `kernel/bpf/helpers.c` – that's the likely central hub. I already know that `CONFIG_BPF_SYSCALL` is a key player here, as it's responsible for building that file. I need to understand the configuration requirements.

Next, I have to figure out if `CONFIG_DEBUG_INFO_BTF` is a hard dependency for these kfuncs or BTF ID related features. I'll need to explore that further. My plan is to start with a good ol' `grep` session. I'll search for things like `BTF_ID_FLAGS`, or even specific functions like `bpf_copy_from_user_str` within the relevant code to understand the relationship between kfuncs and BTF.



6/2 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "bpf_copy_from_user_str",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/helpers.c=3649=__bpf_kfunc void bpf_iter_bits_destroy(struct bpf_iter_bits *it)\n--\nkernel/bpf/helpers.c-3658-/**\nkernel/bpf/helpers.c:3659: * bpf_copy_from_user_str() - Copy a string from an unsafe user address\nkernel/bpf/helpers.c-3660- * @dst:             Destination address, in kernel space.  This buffer must be\n--\nkernel/bpf/helpers.c-3672- */\nkernel/bpf/helpers.c:3673:__bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user *unsafe_ptr__ign, u64 flags)\nkernel/bpf/helpers.c-3674-{\n--\nkernel/bpf/helpers.c=5029=BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)\nkernel/bpf/helpers.c:5030:BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)\nkernel/bpf/helpers.c-5031-BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)\n--\nkernel/bpf/helpers.c=5045=BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE)\nkernel/bpf/helpers.c:5046:BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE)\nkernel/bpf/helpers.c-5047-BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE)\n"
}

7/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 16562
							output: 32
							thoughts: 18
						

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

**Investigating BTF/kfunc Registration and Configuration**

Right, let's dive into the details. I need to refresh my memory on how BTF and kfuncs are actually registered within the kernel, and, just as importantly, what the necessary configuration options are. I'm thinking about the low-level details of the registration process. I should also think about the configuration options in the kernel that enable or disable these features, and the impact of those options. I know these features are pivotal for advanced eBPF capabilities, but the nuances of their set-up are sometimes easily forgotten. I need to make sure I have a solid understanding, and be completely certain of my facts. Time to trace the registration calls and examine the relevant `Kconfig` entries and associated code paths. I want to have a clear understanding of what’s enabled by default, what requires specific compilation flags, and any potential dependencies or conflicts. Good.



8/2 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "BTF_KFUNCS_START",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/arena.c=1118=__bpf_kfunc_end_defs();\nkernel/bpf/arena.c-1119-\nkernel/bpf/arena.c:1120:BTF_KFUNCS_START(arena_kfuncs)\nkernel/bpf/arena.c-1121-BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\n--\nkernel/bpf/cpumask.c=475=__bpf_kfunc_end_defs();\nkernel/bpf/cpumask.c-476-\nkernel/bpf/cpumask.c:477:BTF_KFUNCS_START(cpumask_kfunc_btf_ids)\nkernel/bpf/cpumask.c-478-BTF_ID_FLAGS(func, bpf_cpumask_create, KF_ACQUIRE | KF_RET_NULL)\n--\nkernel/bpf/crypto.c=352=__bpf_kfunc_end_defs();\nkernel/bpf/crypto.c-353-\nkernel/bpf/crypto.c:354:BTF_KFUNCS_START(crypt_init_kfunc_btf_ids)\nkernel/bpf/crypto.c-355-BTF_ID_FLAGS(func, bpf_crypto_ctx_create, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)\n--\nkernel/bpf/crypto.c=360=static const struct btf_kfunc_id_set crypt_init_kfunc_set = {\n--\nkernel/bpf/crypto.c-364-\nkernel/bpf/crypto.c:365:BTF_KFUNCS_START(crypt_kfunc_btf_ids)\nkernel/bpf/crypto.c-366-BTF_ID_FLAGS(func, bpf_crypto_decrypt, KF_RCU)\n--\nkernel/bpf/helpers.c=4888=void bpf_task_work_cancel_and_free(void *val)\n--\nkernel/bpf/helpers.c-4908-\nkernel/bpf/helpers.c:4909:BTF_KFUNCS_START(generic_btf_ids)\nkernel/bpf/helpers.c-4910-#ifdef CONFIG_CRASH_DUMP\n--\nkernel/bpf/helpers.c=4985=BTF_ID(func, bpf_cgroup_release_dtor)\n--\nkernel/bpf/helpers.c-4987-\nkernel/bpf/helpers.c:4988:BTF_KFUNCS_START(common_btf_ids)\nkernel/bpf/helpers.c-4989-BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL)\n--\nkernel/bpf/map_iter.c=219=__bpf_kfunc_end_defs();\nkernel/bpf/map_iter.c-220-\nkernel/bpf/map_iter.c:221:BTF_KFUNCS_START(bpf_map_iter_kfunc_ids)\nkernel/bpf/map_iter.c-222-BTF_ID_FLAGS(func, bpf_map_sum_elem_count)\n--\nkernel/bpf/rqspinlock.c=745=__bpf_kfunc_end_defs();\nkernel/bpf/rqspinlock.c-746-\nkernel/bpf/rqspinlock.c:747:BTF_KFUNCS_START(rqspinlock_kfunc_ids)\nkernel/bpf/rqspinlock.c-748-BTF_ID_FLAGS(func, bpf_res_spin_lock, KF_RET_NULL | KF_SPINLOCK_SAFE)\n"
}

9/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 17808
							output: 37
							thoughts: 24
						

Thoughts:
Here's my thought process, as it went through my mind:

**Checking `common_btf_ids` Registration in `kernel/bpf/helpers.c`**

Okay, I need to verify something about the `common_btf_ids` registration. My mental checklist says to head straight to `kernel/bpf/helpers.c`. I have a feeling I'll find what I'm looking for there, or at least a good starting point. I want to quickly confirm the registration process – likely how these common BTF IDs are defined and associated with their respective helpers. I'm assuming there's some sort of table or macro involved. I should probably be prepared to trace through any relevant initialization routines, just to be thorough. My goal is to quickly pinpoint how these IDs are linked to their corresponding eBPF helper functions, and how they get exposed. I'll need to pay close attention to the data structures involved, the exact registration mechanism, and any relevant macros or helper functions that implement this process. I'm expecting this should be a fairly straightforward check, but as with all things BPF, best to assume nothing until I see it.



10/2 2026/09/08 17:15 tool read-file
0m Args:
{
  "File": "kernel/bpf/helpers.c",
  "FirstLine": 5025,
  "LineCount": 40
}

Results:
{
  "Contents": "5025:\tBTF_ID_FLAGS(func, bpf_preempt_disable)\n5026:\tBTF_ID_FLAGS(func, bpf_preempt_enable)\n5027:\tBTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)\n5028:\tBTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)\n5029:\tBTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)\n5030:\tBTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)\n5031:\tBTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)\n5032:\tBTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)\n5033:\tBTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE)\n5034:\tBTF_ID_FLAGS(func, bpf_get_kmem_cache)\n5035:\tBTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)\n5036:\tBTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)\n5037:\tBTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)\n5038:\tBTF_ID_FLAGS(func, bpf_local_irq_save)\n5039:\tBTF_ID_FLAGS(func, bpf_local_irq_restore)\n5040:\t#ifdef CONFIG_BPF_EVENTS\n5041:\tBTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)\n5042:\tBTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)\n5043:\tBTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)\n5044:\tBTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr)\n5045:\tBTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE)\n5046:\tBTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE)\n5047:\tBTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE)\n5048:\tBTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE)\n5049:\t#endif\n5050:\t#ifdef CONFIG_DMA_SHARED_BUFFER\n5051:\tBTF_ID_FLAGS(func, bpf_iter_dmabuf_new, KF_ITER_NEW | KF_SLEEPABLE)\n5052:\tBTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)\n5053:\tBTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)\n5054:\t#endif\n5055:\tBTF_ID_FLAGS(func, __bpf_trap)\n5056:\tBTF_ID_FLAGS(func, bpf_strcmp);\n5057:\tBTF_ID_FLAGS(func, bpf_strcasecmp);\n5058:\tBTF_ID_FLAGS(func, bpf_strncasecmp);\n5059:\tBTF_ID_FLAGS(func, bpf_strchr);\n5060:\tBTF_ID_FLAGS(func, bpf_strchrnul);\n5061:\tBTF_ID_FLAGS(func, bpf_strnchr);\n5062:\tBTF_ID_FLAGS(func, bpf_strrchr);\n5063:\tBTF_ID_FLAGS(func, bpf_strlen);\n5064:\tBTF_ID_FLAGS(func, bpf_strnlen);\n"
}

11/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 19271
							output: 37
							thoughts: 67
						

Thoughts:
**Diving into the BPF Verifier**

Okay, so I need to refresh my memory on the handling of `BTF_TYPE_SAFE_TRUSTED_OR_NULL` and how it relates to `struct linux_binprm` within the BPF verifier. I know it's in `kernel/bpf/verifier.c`, but let's be precise. I'm going to jump straight to the source and zero in on lines roughly between 6000 and 6070. That should be the core area where I'll find the logic adding that trusted or null check for the structure. I expect to find the relevant code in that section. Time to get to it.



12/2 2026/09/08 17:15 tool read-file
0m Args:
{
  "File": "kernel/bpf/verifier.c",
  "FirstLine": 6000,
  "LineCount": 70
}

Results:
{
  "Contents": "6000:\t\tstruct sched_ext_ops *ops;\n6001:\t};\n6002:\t\n6003:\tBTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {\n6004:\t\tstruct inode *d_inode;\n6005:\t};\n6006:\t\n6007:\tBTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm) {\n6008:\t\tstruct mm_struct *mm;\n6009:\t};\n6010:\t\n6011:\tBTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket) {\n6012:\t\tstruct sock *sk;\n6013:\t};\n6014:\t\n6015:\tBTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct) {\n6016:\t\tstruct mm_struct *vm_mm;\n6017:\t\tstruct file *vm_file;\n6018:\t};\n6019:\t\n6020:\tstatic bool type_is_rcu(struct bpf_verifier_env *env,\n6021:\t\t\t\tstruct bpf_reg_state *reg,\n6022:\t\t\t\tconst char *field_name, u32 btf_id)\n6023:\t{\n6024:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct task_struct));\n6025:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct cgroup));\n6026:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct css_set));\n6027:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU(struct cgroup_subsys_state));\n6028:\t\n6029:\t\treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id, \"__safe_rcu\");\n6030:\t}\n6031:\t\n6032:\tstatic bool type_is_rcu_or_null(struct bpf_verifier_env *env,\n6033:\t\t\t\t\tstruct bpf_reg_state *reg,\n6034:\t\t\t\t\tconst char *field_name, u32 btf_id)\n6035:\t{\n6036:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU_OR_NULL(struct mm_struct));\n6037:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU_OR_NULL(struct sk_buff));\n6038:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_RCU_OR_NULL(struct request_sock));\n6039:\t\n6040:\t\treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id, \"__safe_rcu_or_null\");\n6041:\t}\n6042:\t\n6043:\tstatic bool type_is_trusted(struct bpf_verifier_env *env,\n6044:\t\t\t\t    struct bpf_reg_state *reg,\n6045:\t\t\t\t    const char *field_name, u32 btf_id)\n6046:\t{\n6047:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct bpf_iter_meta));\n6048:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct bpf_iter__task));\n6049:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct linux_binprm));\n6050:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct file));\n6051:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_init_task_args));\n6052:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_cpu_release_args));\n6053:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_attach_args));\n6054:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args));\n6055:\t\n6056:\t\treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id, \"__safe_trusted\");\n6057:\t}\n6058:\t\n6059:\tstatic bool type_is_trusted_or_null(struct bpf_verifier_env *env,\n6060:\t\t\t\t\t    struct bpf_reg_state *reg,\n6061:\t\t\t\t\t    const char *field_name, u32 btf_id)\n6062:\t{\n6063:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));\n6064:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));\n6065:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm));\n6066:\t\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));\n6067:\t\n6068:\t\treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id,\n6069:\t\t\t\t\t\t  \"__safe_trusted_or_null\");\n"
}

13/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 20988
							output: 29
							thoughts: 606
						

Thoughts:
**Analyzing Potential BPF LSM Hooks and Memory Safety in `linux_binprm`**

Okay, so I'm looking at this `linux_binprm` struct and how it interacts with the BPF verifier. I see that the `mm` field is now marked as trusted or null, which immediately piques my interest from a security perspective.  Then I noticed the change in `fs/exec.c` with the `free_bprm` function.  It now sets `bprm->mm = NULL` before releasing the memory with `mmput(mm)`. Aha!  That's the key. This change was implemented because `bprm->mm` is now potentially accessible from BPF LSM hooks on the `bprm` struct.

Essentially, by setting `bprm->mm = NULL` before freeing the structure, we're mitigating potential race conditions, use-after-free vulnerabilities, or dangling pointer accesses if a BPF hook tries to access `bprm->mm` *after* it's been freed or while it's being torn down.  Smart! This is good defensive programming, essential when dealing with memory accessed via BPF.

Now, I want to dive a bit deeper into what these hooks could be doing. I'll need to check the relevant BPF helpers, so I'm going to look at `kernel/bpf/helpers.c`. I'm going to make sure to check the following: `bpf_copy_from_user_task`, `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, `bpf_copy_from_user_task_str`, `__bpf_copy_from_user_mm`, and `__bpf_copy_from_user_mm_str` to see how those are being used.

Also, I'll review `mm/util.c` and `mm/memory.c` to see if there is any interesting code like: `copy_remote_mm_str`, `copy_remote_vm_str` and `__copy_remote_mm_str`.

I should probably also double-check `fs/exec.c`, and specifically `free_bprm`. I should verify if it's `static` or `static inline`.  Because it's a critical function related to the `linux_binprm` struct and its memory management, and I need to confirm whether it is called from outside the source file, for example, from an external BPF hook, or not. The focus symbols need to be one of the following:
1. `bpf_copy_from_user_mm`
2. `bpf_copy_from_user_mm_str`
3. `bpf_copy_from_user_task`
4. `bpf_copy_from_user_task_str`
5. `copy_remote_mm_str`
6. `copy_remote_vm_str`
7. `__copy_remote_mm_str`
8. `free_bprm`



14/2 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "free_bprm",
  "PathPrefix": "fs/exec.c"
}

Results:
{
  "Output": "fs/exec.c=1394=EXPORT_SYMBOL(finalize_exec);\n--\nfs/exec.c-1398- * setup_new_exec() commits the new creds and drops the lock.\nfs/exec.c:1399: * Or, if exec fails before, free_bprm() should release -\u003ecred\nfs/exec.c-1400- * and unlock.\n--\nfs/exec.c=1452=void bprm_drop_loader(struct linux_binprm *bprm)\n--\nfs/exec.c-1456-\nfs/exec.c:1457:static void free_bprm(struct linux_binprm *bprm)\nfs/exec.c-1458-{\n--\nfs/exec.c=1493=static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int flags)\n--\nfs/exec.c-1559-out_free:\nfs/exec.c:1560:\tfree_bprm(bprm);\nfs/exec.c-1561-\treturn ERR_PTR(retval);\n--\nfs/exec.c-1563-\nfs/exec.c:1564:DEFINE_CLASS(bprm, struct linux_binprm *, if (!IS_ERR(_T)) free_bprm(_T),\nfs/exec.c-1565-\talloc_bprm(fd, name, flags), int fd, struct filename *name, int flags)\n"
}

15/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 22674
							output: 30
							thoughts: 232
						

Thoughts:
**My BPF-Related Thoughts**

Okay, let's dive in. First, I need to refresh my memory on `free_bprm`. Right, it's a `static void` function – `free_bprm(struct linux_binprm *bprm)`. I recall `alloc_bprm` calls it, and the macro `DEFINE_CLASS(bprm, ...)` also utilizes it. Important to remember those connections.

Next, I need to confirm the BPF configurations. I know we need `BPF_SYSCALL` for the BPF helpers and kfuncs, which are found in `helpers.c`, `copy_remote_mm_str` in `mm/util.c`, and the verifier code. Then, there's `BPF_LSM` – absolutely crucial for BPF LSM programs that require access to `bprm->mm`.

Now, onto verifying something about `BTF_ID_FLAGS`. I need to solidify my understanding of how that works and, crucially, if `CONFIG_DEBUG_INFO_BTF` is *truly* a hard requirement for kfuncs. I know that kfunc resolution within kernel BPF relies on BTF metadata generated from `CONFIG_DEBUG_INFO_BTF`, but let's just make sure. I'll search `kernel/bpf/` for the `CONFIG_DEBUG_INFO_BTF` symbol to make sure I haven't missed anything.



16/2 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "DEBUG_INFO_BTF",
  "PathPrefix": "kernel/bpf"
}

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

17/2 2026/09/08 17:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 25202
							output: 259
							thoughts: 576
						

Thoughts:
**My Analysis of the Code Changes**

Okay, so I'm seeing that `CONFIG_DEBUG_INFO_BTF` is essential here, specifically because kfuncs rely on it; no BTF, no kfunc calls. And obviously, `CONFIG_BPF_SYSCALL` is a given. Let's dig into the details. I need to make sure I haven't missed anything.

Right, I'm noting these changes in particular: new kfuncs `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` in `kernel/bpf/helpers.c`, and modifications to `bpf_copy_from_user_task` and `bpf_copy_from_user_task_str` in the same file. Also, `copy_remote_mm_str` is a new function and `copy_remote_vm_str` and `__copy_remote_mm_str` were modified in `mm/util.c` and `mm/memory.c` respectively. I should double-check the configs: `BPF_SYSCALL` and `DEBUG_INFO_BTF` are present.

Essentially, this patch is introducing or modifying functions related to copying from user space, and specifically making those functions reachable from BPF programs. That triggers my fuzzing instincts. The BPF verifier is going to treat `bprm->mm` field as trusted, which looks like a pointer dereference. Setting it to NULL in `free_bprm()` is a good mitigation, but still, this is prime fuzzing territory.

My focus should be on the impact these changes have on the reachable BPF and memory management syscalls and APIs.

So, the key symbols I need to verify are: `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, `bpf_copy_from_user_task`, `bpf_copy_from_user_task_str`, `copy_remote_mm_str`, `copy_remote_vm_str`, and `__copy_remote_mm_str`. They're all specific to this patch, and I think they will all be reachable. This seems like a complete, focused analysis. Time to record my findings.



18/2 2026/09/08 17:15 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "bpf_copy_from_user_task",
    "bpf_copy_from_user_task_str",
    "copy_remote_mm_str",
    "copy_remote_vm_str",
    "__copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm, bpf_copy_from_user_mm_str), refactors existing helpers and kfuncs (bpf_copy_from_user_task, bpf_copy_from_user_task_str), adds copy_remote_mm_str / __copy_remote_mm_str for reading strings from remote mm_struct instances, and allows trusted access to bprm-\u003emm in the BPF verifier. These changes touch core reachable BPF and MM logic and warrant fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL",
    "DEBUG_INFO_BTF"
  ],
  "FocusSymbols": [
    "bpf_copy_from_user_mm",
    "bpf_copy_from_user_mm_str",
    "bpf_copy_from_user_task",
    "bpf_copy_from_user_task_str",
    "copy_remote_mm_str",
    "copy_remote_vm_str",
    "__copy_remote_mm_str"
  ],
  "Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm, bpf_copy_from_user_mm_str), refactors existing helpers and kfuncs (bpf_copy_from_user_task, bpf_copy_from_user_task_str), adds copy_remote_mm_str / __copy_remote_mm_str for reading strings from remote mm_struct instances, and allows trusted access to bprm-\u003emm in the BPF verifier. These changes touch core reachable BPF and MM logic and warrant fuzzing.",
  "WorthFuzzing": true
}

19/1 2026/09/08 17:15 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

20/2 2026/09/08 17:15 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch introduces new BPF kfuncs (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`) and refactors existing helpers (`bpf_copy_from_user_task` and `bpf_copy_from_user_task_str`) along with `copy_remote_vm_str`/`copy_remote_mm_str` helpers to read user memory from an `mm_struct`. It also sets `bprm-\u003emm = NULL` in `free_bprm()`.\n\nAll destination buffers are written either directly via `access_remote_vm`/`copy_remote_mm_str` or explicitly cleared with `memset` on error and padding conditions. No new uninitialized stack, heap, or page allocations are introduced or passed across trust boundaries without initialization. Potential bugs introduced by these changes (such as lifetime issues, NULL pointer dereferences, refcounting issues, or out-of-bounds accesses) are covered by KASAN and standard kernel debug tooling. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

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

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

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

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

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

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

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


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

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit a94c69a754610027e65484417ca4ae54dcd4f740
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 8 17:14:54 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/exec.c b/fs/exec.c
index 745f6eb5279e6..4ddd403fd91c7 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1456,9 +1456,12 @@ void bprm_drop_loader(struct linux_binprm *bprm)
 
 static void free_bprm(struct linux_binprm *bprm)
 {
-	if (bprm->mm) {
+	struct mm_struct *mm = bprm->mm;
+
+	if (mm) {
 		acct_arg_size(bprm, 0);
-		mmput(bprm->mm);
+		bprm->mm = NULL;
+		mmput(mm);
 	}
 	if (bprm->user_ns)
 		put_user_ns(bprm->user_ns);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23e..6f10ce315eaa8 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3325,10 +3325,10 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
 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_vm_str(struct task_struct *tsk, unsigned long addr,
-			      void *buf, int len, unsigned int gup_flags);
-#endif
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags);
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags);
 
 long get_user_pages_remote(struct mm_struct *mm,
 			   unsigned long start, unsigned long nr_pages,
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..3338bebdd86e7 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -679,9 +679,44 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
 	.arg3_type	= ARG_ANYTHING,
 };
 
+static int __bpf_copy_from_user_mm(void *dst, u32 size,
+				   const void __user *user_ptr,
+				   struct mm_struct *mm)
+{
+	int ret;
+
+	ret = access_remote_vm(mm, (unsigned long)user_ptr, dst, size, 0);
+	if (ret == size)
+		return 0;
+
+	memset(dst, 0, size);
+	/* Return -EFAULT for partial read */
+	return ret < 0 ? ret : -EFAULT;
+}
+
+static int __bpf_copy_from_user_mm_str(void *dst, u32 size,
+				       const void __user *user_ptr,
+				       struct mm_struct *mm, u64 flags)
+{
+	int ret;
+
+	ret = copy_remote_mm_str(mm, (unsigned long)user_ptr, dst, size, 0);
+	if (ret < 0) {
+		if (flags & BPF_F_PAD_ZEROS)
+			memset(dst, 0, size);
+		return ret;
+	}
+
+	if (flags & BPF_F_PAD_ZEROS)
+		memset(dst + ret, 0, size - ret);
+
+	return ret + 1;
+}
+
 BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
 	   const void __user *, user_ptr, struct task_struct *, tsk, u64, flags)
 {
+	struct mm_struct *mm;
 	int ret;
 
 	/* flags is not used yet */
@@ -691,13 +726,16 @@ BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
 	if (unlikely(!size))
 		return 0;
 
-	ret = access_process_vm(tsk, (unsigned long)user_ptr, dst, size, 0);
-	if (ret == size)
-		return 0;
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		memset(dst, 0, size);
+		return -EFAULT;
+	}
 
-	memset(dst, 0, size);
-	/* Return -EFAULT for partial read */
-	return ret < 0 ? ret : -EFAULT;
+	ret = __bpf_copy_from_user_mm(dst, size, user_ptr, mm);
+	mmput(mm);
+
+	return ret;
 }
 
 const struct bpf_func_proto bpf_copy_from_user_task_proto = {
@@ -3658,6 +3696,68 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user
 	return ret + 1;
 }
 
+/**
+ * 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 an attempted copy cannot be completed in full. Unsupported
+ * flags return -EINVAL without modifying @dst.
+ *
+ * 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)
+{
+	if (unlikely(flags))
+		return -EINVAL;
+
+	if (unlikely(!dst__sz))
+		return 0;
+
+	return __bpf_copy_from_user_mm(dst, dst__sz, unsafe_ptr__ign, mm);
+}
+
+/**
+ * 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 the flags are valid and BPF_F_PAD_ZEROS is set, the unused portion of
+ * @dst is cleared on success and all of @dst is cleared on a copy failure.
+ * Unsupported flags return -EINVAL without modifying @dst.
+ *
+ * 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)
+{
+	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+		return -EINVAL;
+
+	if (unlikely(dst__sz == 0))
+		return 0;
+
+	return __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+					   mm, flags);
+}
+
 /**
  * bpf_copy_from_user_task_str() - Copy a string from an task's address space
  * @dst:             Destination address, in kernel space.  This buffer must be
@@ -3681,6 +3781,7 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
 					    const void __user *unsafe_ptr__ign,
 					    struct task_struct *tsk, u64 flags)
 {
+	struct mm_struct *mm;
 	int ret;
 
 	if (unlikely(flags & ~BPF_F_PAD_ZEROS))
@@ -3689,17 +3790,20 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
 	if (unlikely(dst__sz == 0))
 		return 0;
 
-	ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
-	if (ret < 0) {
+	mm = get_task_mm(tsk);
+	if (!mm) {
 		if (flags & BPF_F_PAD_ZEROS)
 			memset(dst, 0, dst__sz);
-		return ret;
+		else
+			*(char *)dst = '\0';
+		return -EFAULT;
 	}
 
-	if (flags & BPF_F_PAD_ZEROS)
-		memset(dst + ret, 0, dst__sz - ret);
+	ret = __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+					  mm, flags);
+	mmput(mm);
 
-	return ret + 1;
+	return ret;
 }
 
 /* Keep unsigned long in prototype so that kfunc is usable when emitted to
@@ -4924,6 +5028,8 @@ BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)
 BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)
 BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)
+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_copy_from_user_task_str, KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_get_kmem_cache)
 BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e79750e24808..791b3d25caa54 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6004,6 +6004,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;
 };
@@ -6058,6 +6062,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/mm/internal.h b/mm/internal.h
index 38b1165212c94..557b293813557 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -25,6 +25,9 @@
 struct folio_batch;
 struct hstate;
 
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags);
+
 struct huge_bootmem_page {
 	struct list_head list;
 	struct hstate *hstate;
diff --git a/mm/memory.c b/mm/memory.c
index 8b0c2c735d3de..fe2f5e988fb90 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7331,8 +7331,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);
  * Copy a string from another process's address space as given in mm.
  * If there is any error return -EFAULT.
  */
-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
-				void *buf, int len, unsigned int gup_flags)
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags)
 {
 	void *old_buf = buf;
 	int err = 0;
@@ -7407,43 +7407,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 		return err;
 	return buf - old_buf;
 }
-
-/**
- * copy_remote_vm_str - copy a string from another process's address space.
- * @tsk:	the task of the target 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 guaranteed to leave NUL-terminated
- * buffer. On any error, return -EFAULT.
- */
-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
-		       void *buf, int len, unsigned int gup_flags)
-{
-	struct mm_struct *mm;
-	int ret;
-
-	if (unlikely(len == 0))
-		return 0;
-
-	mm = get_task_mm(tsk);
-	if (!mm) {
-		*(char *)buf = '\0';
-		return -EFAULT;
-	}
-
-	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
-
-	mmput(mm);
-
-	return ret;
-}
-EXPORT_SYMBOL_GPL(copy_remote_vm_str);
 #endif /* CONFIG_BPF_SYSCALL */
 
 /*
diff --git a/mm/nommu.c b/mm/nommu.c
index 498e01ee40b05..98596e60311f3 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1746,8 +1746,8 @@ EXPORT_SYMBOL_GPL(access_process_vm);
  * Copy a string from another process's address space as given in mm.
  * If there is any error return -EFAULT.
  */
-static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
-				void *buf, int len)
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			 void *buf, int len, unsigned int gup_flags)
 {
 	unsigned long addr_end;
 	struct vm_area_struct *vma;
@@ -1781,43 +1781,6 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	mmap_read_unlock(mm);
 	return ret;
 }
-
-/**
- * copy_remote_vm_str - copy a string from another process's address space.
- * @tsk:	the task of the target 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 guaranteed to leave NUL-terminated
- * buffer. On any error, return -EFAULT.
- */
-int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
-		       void *buf, int len, unsigned int gup_flags)
-{
-	struct mm_struct *mm;
-	int ret;
-
-	if (unlikely(len == 0))
-		return 0;
-
-	mm = get_task_mm(tsk);
-	if (!mm) {
-		*(char *)buf = '\0';
-		return -EFAULT;
-	}
-
-	ret = __copy_remote_vm_str(mm, addr, buf, len);
-
-	mmput(mm);
-
-	return ret;
-}
-EXPORT_SYMBOL_GPL(copy_remote_vm_str);
 #endif /* CONFIG_BPF_SYSCALL */
 
 /**
diff --git a/mm/util.c b/mm/util.c
index bf0513d1d3d08..2eca27b02791f 100644
--- a/mm/util.c
+++ b/mm/util.c
@@ -1061,6 +1061,68 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen)
 	return res;
 }
 
+#ifdef CONFIG_BPF_SYSCALL
+/**
+ * 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. If @len is zero, return 0 without accessing
+ * @buf. Otherwise, @buf is always NUL-terminated. 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_mm_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
+ * @addr:	start address to read from
+ * @buf:	destination buffer
+ * @len:	number of bytes to copy
+ * @gup_flags:	flags modifying lookup behaviour
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. If @len is zero, return 0 without accessing
+ * @buf. Otherwise, @buf is always NUL-terminated. On any error, return
+ * -EFAULT.
+ */
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+		       void *buf, int len, unsigned int gup_flags)
+{
+	struct mm_struct *mm;
+	int ret;
+
+	if (unlikely(len == 0))
+		return 0;
+
+	mm = get_task_mm(tsk);
+	if (!mm) {
+		*(char *)buf = '\0';
+		return -EFAULT;
+	}
+
+	ret = __copy_remote_mm_str(mm, addr, buf, len, gup_flags);
+
+	mmput(mm);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(copy_remote_vm_str);
+#endif /* CONFIG_BPF_SYSCALL */
+
 int __weak memcmp_pages(struct page *page1, struct page *page2)
 {
 	char *addr1, *addr2;
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..b2325b1935761
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
@@ -0,0 +1,72 @@
+// 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)
+{
+	char arg0[] = "first";
+	char arg1[] = "second-argument";
+	char env0[] = "SOME_ENV=a";
+	char env1[] = "OTHER_ENV=something";
+	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;
+
+	/*
+	 * On !CONFIG_MMU, exec strings are held in bprm->page[] rather than
+	 * being mapped in bprm->mm.
+	 */
+	if (!skel->kconfig->CONFIG_MMU) {
+		printf("%s:SKIP: test requires CONFIG_MMU\n", __func__);
+		test__skip();
+		goto out;
+	}
+
+	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[] = { arg0, arg1, NULL };
+		char *const envp[] = { env0, env1, NULL };
+
+		skel->bss->monitored_pid = getpid();
+		execve("/bin/true", argv, envp);
+		_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->bprm_envc, 2, "bprm_envc");
+	ASSERT_EQ(skel->bss->data_len_match, 1, "data_len_match");
+	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, sizeof(arg0), "str_arg0_ret");
+	ASSERT_EQ(skel->bss->str_arg1_ret, sizeof(arg1), "str_arg1_ret");
+	ASSERT_EQ(skel->bss->str_env0_ret, sizeof(env0), "str_env0_ret");
+	ASSERT_EQ(skel->bss->str_env1_ret, sizeof(env1), "str_env1_ret");
+	ASSERT_EQ(skel->bss->data_match, 1, "data_match");
+	ASSERT_EQ(skel->bss->str_args_match, 1, "str_args_match");
+	ASSERT_EQ(skel->bss->str_envs_match, 1, "str_envs_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..b334a157419e5
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
@@ -0,0 +1,123 @@
+// 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_data[] = "first\0second-argument\0"
+				    "SOME_ENV=a\0OTHER_ENV=something";
+static const char expected_arg0[] = "first";
+static const char expected_arg1[] = "second-argument";
+static const char expected_env0[] = "SOME_ENV=a";
+static const char expected_env1[] = "OTHER_ENV=something";
+
+int monitored_pid;
+int bprm_argc;
+int bprm_envc;
+int data_len_match;
+int invalid_flags_ret;
+int copy_ret;
+int str_arg0_ret;
+int str_arg1_ret;
+int str_env0_ret;
+int str_env1_ret;
+int data_match;
+int str_args_match;
+int str_envs_match;
+
+extern bool CONFIG_MMU __kconfig __weak;
+
+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)
+{
+	u32 pid = bpf_get_current_pid_tgid() >> 32;
+	char data[sizeof(expected_data)] = {};
+	struct mm_struct *mm;
+	char arg0[32] = {};
+	char arg1[32] = {};
+	char env0[32] = {};
+	char env1[32] = {};
+	u64 offset = 0;
+	u64 data_len;
+
+	if (!CONFIG_MMU)
+		return 0;
+
+	if (pid != monitored_pid)
+		return 0;
+
+	mm = bprm->mm;
+	if (!mm)
+		return 0;
+
+	bprm_argc = bprm->argc;
+	bprm_envc = bprm->envc;
+
+	/* this is the total size of args and envs starting from bprm->p */
+	data_len = bprm->exec - bprm->p;
+	data_len_match = data_len == sizeof(expected_data);
+
+	invalid_flags_ret = bpf_copy_from_user_mm(data,
+						  sizeof(data), (void *)bprm->p, mm, ~0ULL);
+
+	copy_ret = bpf_copy_from_user_mm(data, sizeof(data), (void *)bprm->p,
+					 mm, 0);
+	if (copy_ret)
+		return 0;
+
+	data_match =
+		!__builtin_memcmp(data, expected_data, sizeof(expected_data));
+
+	/* arg0 is at bprm->p */
+	str_arg0_ret = bpf_copy_from_user_mm_str(arg0, sizeof(arg0),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg0_ret != sizeof(expected_arg0))
+		return 0;
+	offset += str_arg0_ret;
+
+	/* arg1 is at bprm->p + sizeof(arg0) */
+	str_arg1_ret = bpf_copy_from_user_mm_str(arg1, sizeof(arg1),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_arg1_ret != sizeof(expected_arg1))
+		return 0;
+	offset += str_arg1_ret;
+
+	/* env0 is at bprm->p + sizeof(arg0) + sizeof(arg1) */
+	str_env0_ret = bpf_copy_from_user_mm_str(env0, sizeof(env0),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_env0_ret != sizeof(expected_env0))
+		return 0;
+	offset += str_env0_ret;
+
+	/* env1 is at bprm->p + sizeof(arg0) + sizeof(arg1) + sizeof(env0) */
+	str_env1_ret = bpf_copy_from_user_mm_str(env1, sizeof(env1),
+						 (void *)(bprm->p + offset),
+						 mm, BPF_F_PAD_ZEROS);
+	if (str_env1_ret != sizeof(expected_env1))
+		return 0;
+
+	str_args_match =
+		!__builtin_memcmp(arg0, expected_arg0, sizeof(expected_arg0)) &&
+		!__builtin_memcmp(arg1, expected_arg1, sizeof(expected_arg1));
+	str_envs_match =
+		!__builtin_memcmp(env0, expected_env0, sizeof(expected_env0)) &&
+		!__builtin_memcmp(env1, expected_env1, sizeof(expected_env1));
+
+	return data_match && str_args_match && str_envs_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)

21/3 2026/09/08 17:15 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 10734
							output: 30
							thoughts: 671
						

Thoughts:
**Analyzing This Patch Series**

Okay, let's break this down. My initial impression is that this series is centered around enhancing BPF's ability to safely and correctly handle memory operations from user space within a different MM context, specifically focusing on `struct linux_binprm` and, by extension, the execution environment.

First, I see a crucial cleanup in `fs/exec.c` within `free_bprm`. The addition of the `mmput(mm)` call after nullifying `bprm->mm` is good; it ensures proper MM reference counting and prevents potential leaks if the `bprm` outlives its `mm_struct`. The `acct_arg_size` call is also worth noting; probably relates to accounting for memory allocated to the arguments for exec.

The inclusion of declarations for `copy_remote_mm_str` and `copy_remote_vm_str` in `include/linux/mm.h` is a red flag in a good way; it is probably the core of the change. They're definitely related to memory copy operations from remote memory contexts. I'll need to dig into their definitions in a bit.

The meat of this series lies in `kernel/bpf/helpers.c`. We're getting new BPF helper functions. The addition of `__bpf_copy_from_user_mm` and `__bpf_copy_from_user_mm_str`, along with the corresponding kfuncs, clearly indicates a move towards more robust and controlled memory access within BPF programs. The use of `access_remote_vm` and the careful handling of partial reads and errors, with `memset` calls to zero out uninitialized portions, is exactly what I would expect to see for memory safety. I want to see if this is being integrated everywhere. I like that we're refactoring existing functions like `bpf_copy_from_user_task` to use the new `__bpf_copy_from_user_mm` and similar pattern for strings. The handling of the `BPF_F_PAD_ZEROS` flag is also critical for avoiding information leaks from uninitialized memory.

The modification in `kernel/bpf/verifier.c` allowing access to `struct mm_struct *mm` in `struct linux_binprm` looks like a necessary change to enable the new functionality. It indicates that the verifier is being updated to permit the access needed by the new helpers. This is a very targeted change, and that's good.

The refactoring in the `mm/` files, specifically the changes to `copy_remote_vm_str` to share `__copy_remote_mm_str`, is great. Code reuse is key, and it suggests a well-thought-out design. The addition of `copy_remote_mm_str` is interesting because it means it can operate on a broader context of memory.

Finally, the inclusion of selftests is excellent. It demonstrates a commitment to verifying the correctness and safety of these changes. I'll need to look at those in detail to confirm they cover the edge cases and potentially problematic scenarios.

Now, as an expert in this, I have some concerns. I have to think about memory safety. I want to ensure that there are no uninitialized memory reads or leaks. I’ll need to check the implementation of `__copy_remote_mm_str` (and related functions) in `mm/memory.c`, `mm/nommu.c`, and elsewhere to see exactly how it writes to the buffer and how it handles string termination and length. I must verify there aren't any potential vulnerabilities, especially when dealing with data coming from potentially untrusted user space. I must check, in particular, whether the BPF verifier enforces suitable constraints and checks on the size and source of the memory copy operations to prevent buffer overflows or other memory corruption issues. Overall, it looks like a well-structured series, but I need to make sure the implementation is bulletproof.



22/3 2026/09/08 17:15 tool grepper
0m Args:
{
  "Expression": "__copy_remote_mm_str",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/internal.h=26=struct hstate;\nmm/internal.h-27-\nmm/internal.h:28:int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/internal.h-29-\t\t\t void *buf, int len, unsigned int gup_flags);\n--\nmm/memory.c=7327=EXPORT_SYMBOL_GPL(access_process_vm);\n--\nmm/memory.c-7333- */\nmm/memory.c:7334:int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/memory.c-7335-\t\t\t void *buf, int len, unsigned int gup_flags)\n--\nmm/nommu.c=1742=EXPORT_SYMBOL_GPL(access_process_vm);\n--\nmm/nommu.c-1748- */\nmm/nommu.c:1749:int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/nommu.c-1750-\t\t\t void *buf, int len, unsigned int gup_flags)\n--\nmm/util.c=1080=int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n--\nmm/util.c-1085-\nmm/util.c:1086:\treturn __copy_remote_mm_str(mm, addr, buf, len, gup_flags);\nmm/util.c-1087-}\n--\nmm/util.c=1102=int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n--\nmm/util.c-1116-\nmm/util.c:1117:\tret = __copy_remote_mm_str(mm, addr, buf, len, gup_flags);\nmm/util.c-1118-\n"
}

23/3 2026/09/08 17:15 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 12777
							output: 34
							thoughts: 22
						

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

**Examining `__copy_remote_mm_str` in `mm/memory.c`**

Okay, so I need to take a look at `__copy_remote_mm_str`. My mind immediately jumps to `mm/memory.c`, that's where I'd expect to find this function. I know this is a core part of the memory management subsystem, so I'm already anticipating some complexities, likely related to remote memory copies, and string manipulation within the kernel. The name itself, `__copy_remote_mm_str`, hints at moving a string between address spaces, likely when dealing with processes or perhaps even userspace interactions from kernel space. I'll need to carefully dissect how it handles virtual addresses, page faults (if any), and any synchronization primitives it employs. Let's dig in and see exactly how it's implemented.



24/3 2026/09/08 17:15 tool read-file
0m Args:
{
  "File": "mm/memory.c",
  "FirstLine": 7330,
  "LineCount": 80
}

Results:
{
  "Contents": "7330:\t/*\n7331:\t * Copy a string from another process's address space as given in mm.\n7332:\t * If there is any error return -EFAULT.\n7333:\t */\n7334:\tint __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n7335:\t\t\t\t void *buf, int len, unsigned int gup_flags)\n7336:\t{\n7337:\t\tvoid *old_buf = buf;\n7338:\t\tint err = 0;\n7339:\t\n7340:\t\t*(char *)buf = '\\0';\n7341:\t\n7342:\t\tif (mmap_read_lock_killable(mm))\n7343:\t\t\treturn -EFAULT;\n7344:\t\n7345:\t\taddr = untagged_addr_remote(mm, addr);\n7346:\t\n7347:\t\t/* Avoid triggering the temporary warning in __get_user_pages */\n7348:\t\tif (!vma_lookup(mm, addr)) {\n7349:\t\t\terr = -EFAULT;\n7350:\t\t\tgoto out;\n7351:\t\t}\n7352:\t\n7353:\t\twhile (len) {\n7354:\t\t\tint bytes, offset, retval;\n7355:\t\t\tvoid *maddr;\n7356:\t\t\tstruct folio *folio;\n7357:\t\t\tstruct page *page;\n7358:\t\t\tstruct vm_area_struct *vma = NULL;\n7359:\t\n7360:\t\t\tpage = get_user_page_vma_remote(mm, addr, gup_flags, \u0026vma);\n7361:\t\t\tif (IS_ERR(page)) {\n7362:\t\t\t\t/*\n7363:\t\t\t\t * Treat as a total failure for now until we decide how\n7364:\t\t\t\t * to handle the CONFIG_HAVE_IOREMAP_PROT case and\n7365:\t\t\t\t * stack expansion.\n7366:\t\t\t\t */\n7367:\t\t\t\t*(char *)buf = '\\0';\n7368:\t\t\t\terr = -EFAULT;\n7369:\t\t\t\tgoto out;\n7370:\t\t\t}\n7371:\t\n7372:\t\t\tfolio = page_folio(page);\n7373:\t\t\tbytes = len;\n7374:\t\t\toffset = addr \u0026 (PAGE_SIZE - 1);\n7375:\t\t\tif (bytes \u003e PAGE_SIZE - offset)\n7376:\t\t\t\tbytes = PAGE_SIZE - offset;\n7377:\t\n7378:\t\t\tmaddr = kmap_local_folio(folio, folio_page_idx(folio, page) * PAGE_SIZE);\n7379:\t\t\tretval = strscpy(buf, maddr + offset, bytes);\n7380:\t\t\tif (retval \u003e= 0) {\n7381:\t\t\t\t/* Found the end of the string */\n7382:\t\t\t\tbuf += retval;\n7383:\t\t\t\tfolio_release_kmap(folio, maddr);\n7384:\t\t\t\tbreak;\n7385:\t\t\t}\n7386:\t\n7387:\t\t\tbuf += bytes - 1;\n7388:\t\t\t/*\n7389:\t\t\t * Because strscpy always NUL terminates we need to\n7390:\t\t\t * copy the last byte in the page if we are going to\n7391:\t\t\t * load more pages\n7392:\t\t\t */\n7393:\t\t\tif (bytes != len) {\n7394:\t\t\t\taddr += bytes - 1;\n7395:\t\t\t\tcopy_from_user_page(vma, page, addr, buf, maddr + (PAGE_SIZE - 1), 1);\n7396:\t\t\t\tbuf += 1;\n7397:\t\t\t\taddr += 1;\n7398:\t\t\t}\n7399:\t\t\tlen -= bytes;\n7400:\t\n7401:\t\t\tfolio_release_kmap(folio, maddr);\n7402:\t\t}\n7403:\t\n7404:\tout:\n7405:\t\tmmap_read_unlock(mm);\n7406:\t\tif (err)\n7407:\t\t\treturn err;\n7408:\t\treturn buf - old_buf;\n7409:\t}\n"
}

25/3 2026/09/08 17:15 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 14143
							output: 257
							thoughts: 1535
						

Thoughts:
**Patch Analysis: Determining KMSAN Necessity**

Okay, let's break down these patch changes and decide if we need to fire up KMSAN. First, I'm going to look at the `free_bprm` function, specifically how it handles the `mm_struct`. It's good to see that `bprm->mm` is nulled out after accounting and before the `mmput` call. This is a crucial step to avoid potential use-after-free scenarios, especially with BPF accessing this data.

Next, the BPF helper functions, like `__bpf_copy_from_user_mm`, and `__bpf_copy_from_user_mm_str` are next up on the list. I'm focusing on their handling of the destination buffer (`dst`) when a copy fails. The explicit `memset(dst, 0, size)` or `memset` padding in various failure paths is exactly what I want to see. This ensures no uninitialized data leaks out. `copy_remote_mm_str`'s guaranteed null termination (or clearing) in addition to padding with zeros if requested (with `BPF_F_PAD_ZEROS`) is a strong indicator of careful design.

The `bpf_copy_from_user_task` and `bpf_copy_from_user_task_str` functions are similar.  They also zero the `dst` buffer on error (or pad it, in the string version). This consistency is reassuring. I also note that the use of flags is limited to `BPF_F_PAD_ZEROS`.

Finally, the verifier change - the addition of `struct linux_binprm` to `type_is_trusted_or_null` for `struct mm_struct *mm` - is important for security. It helps to ensure that memory accesses are controlled.

Now, let's connect all the dots and see if this requires KMSAN. The BPF programs' provided destination buffers are validated by the verifier - so the size and validity are verified - the data is either copied from the remote mm using existing standard functions such as `access_remote_vm` and `copy_remote_mm_str` which are thoroughly tested. And the error paths, or padding paths all involve explicitly zeroing the data. `free_bprm` has an explicit assignment. So, there is no chance of uninitialized memory vulnerabilities or info-leaks, based on these changes. The existing protections of KASAN/standard sanitizers would cover any issues here such as pointer dereferences, out of bounds accesses, or refcount bugs.

Following the guidelines, KMSAN is for cases where we introduce, or expose, UNINITIALIZED MEMORY risks. The code already has initialization, zeroing, or padding. Therefore, because the patch doesn't introduce any new risk of uninitialized memory usage or information leaks, `NeedsKMSAN = false`. I'm confident in this assessment. All the memory is either explicitly initialized or zeroed on any error, so standard bug detectors are sufficient. I will prepare my response accordingly.



26/3 2026/09/08 17:15 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch introduces new BPF kfuncs (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`) and refactors existing helpers (`bpf_copy_from_user_task` and `bpf_copy_from_user_task_str`) along with `copy_remote_vm_str`/`copy_remote_mm_str` helpers to read user memory from an `mm_struct`. It also sets `bprm-\u003emm = NULL` in `free_bprm()`.\n\nAll destination buffers are written either directly via `access_remote_vm`/`copy_remote_mm_str` or explicitly cleared with `memset` on error and padding conditions. No new uninitialized stack, heap, or page allocations are introduced or passed across trust boundaries without initialization. Potential bugs introduced by these changes (such as lifetime issues, NULL pointer dereferences, refcounting issues, or out-of-bounds accesses) are covered by KASAN and standard kernel debug tooling. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch introduces new BPF kfuncs (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`) and refactors existing helpers (`bpf_copy_from_user_task` and `bpf_copy_from_user_task_str`) along with `copy_remote_vm_str`/`copy_remote_mm_str` helpers to read user memory from an `mm_struct`. It also sets `bprm-\u003emm = NULL` in `free_bprm()`.\n\nAll destination buffers are written either directly via `access_remote_vm`/`copy_remote_mm_str` or explicitly cleared with `memset` on error and padding conditions. No new uninitialized stack, heap, or page allocations are introduced or passed across trust boundaries without initialization. Potential bugs introduced by these changes (such as lifetime issues, NULL pointer dereferences, refcounting issues, or out-of-bounds accesses) are covered by KASAN and standard kernel debug tooling. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

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