| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/07 20:20 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF",
"BPF_LSM"
],
"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",
"check_ptr_to_btf_access",
"type_is_trusted_or_null",
"check_mem_access",
"bpf_convert_ctx_accesses",
"free_bprm"
],
"KMSANReasoning": "The patch series introduces BPF kfuncs for copying memory from an arbitrary `struct mm_struct` (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`), refactors existing task-based helpers to use these functions, clears `bprm-\u003emm` before dropping the reference in `free_bprm()`, and updates the BPF verifier to allow fault-protected loads through trusted-or-null BTF pointers (`bpf_is_trusted_or_null_btf_ptr`).\n\nKMSAN vs KASAN Applicability:\n- KMSAN detects reads of uninitialized stack/heap/page memory and kernel-to-user memory disclosure (info-leaks).\n- KASAN / LOCKDEP detect memory corruptions, out-of-bounds accesses, use-after-free conditions, and locking errors.\n- The new BPF copy kfuncs properly handle memory initialization: any destination buffers are either fully written or explicitly zeroed via `memset()` upon failure, partial reads, or when `BPF_F_PAD_ZEROS` is requested.\n- No uninitialized struct fields, stack allocations, or kernel-to-user leaks are introduced or exposed.\n- Verifier type tracking and reference counting changes relate to pointer validity and fault handling, areas where KASAN and standard kernel debugging facilities are applicable, not KMSAN.\n\nTherefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm and bpf_copy_from_user_mm_str), refactors remote MM string copying helpers across mm/ and kernel/bpf/, modifies the BPF verifier to allow fault-protected loads through trusted-or-null pointers, and alters mm cleanup in free_bprm(). These changes touch core BPF and memory management execution paths reachable in standard QEMU environments via BPF and exec syscalls.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/07 20:20 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit db77af66b3aa07b13dc83aafca1b8b0a0291a1fb\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Sep 7 20:20:53 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/bpf_verifier.h b/include/linux/bpf_verifier.h\nindex 9727df5af83ab..4f032ad83c675 100644\n--- a/include/linux/bpf_verifier.h\n+++ b/include/linux/bpf_verifier.h\n@@ -1339,6 +1339,11 @@ static inline bool bpf_is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)\n \t}\n }\n \n+static inline bool bpf_is_trusted_or_null_btf_ptr(enum bpf_reg_type type)\n+{\n+\treturn type == (PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL);\n+}\n+\n static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)\n {\n \t/*\n@@ -1346,7 +1351,9 @@ static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)\n \t * protection, that is, the ones bpf_convert_ctx_accesses() has to\n \t * turn a BPF_LDX into a BPF_PROBE_MEM one for.\n \t */\n-\treturn type == PTR_TO_BTF_ID || (type_flag(type) \u0026 PTR_UNTRUSTED);\n+\treturn type == PTR_TO_BTF_ID ||\n+\t (type_flag(type) \u0026 PTR_UNTRUSTED) ||\n+\t bpf_is_trusted_or_null_btf_ptr(type);\n }\n \n static inline bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)\ndiff --git a/include/linux/mm.h b/include/linux/mm.h\nindex dd09c438fa23e..d5bde1f71a97e 100644\n--- a/include/linux/mm.h\n+++ b/include/linux/mm.h\n@@ -3326,6 +3326,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,\n \t\tvoid *buf, int len, unsigned int gup_flags);\n \n #ifdef CONFIG_BPF_SYSCALL\n+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\n+\t\t\t void *buf, int len, unsigned int gup_flags);\n extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,\n \t\t\t void *buf, int len, unsigned int gup_flags);\n #endif\ndiff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c\nindex b3cc5c8fc8756..d3c564437ad06 100644\n--- a/kernel/bpf/helpers.c\n+++ b/kernel/bpf/helpers.c\n@@ -32,6 +32,10 @@\n \n #include \"../../lib/kstrtox.h\"\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 /* If kernel subsystem is allowing eBPF programs to call this function,\n * inside its own verifier_ops-\u003eget_func_proto() callback it should return\n * bpf_map_lookup_elem_proto, so that verifier can properly check the arguments\n@@ -682,22 +686,15 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {\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-\tif (unlikely(flags))\n-\t\treturn -EINVAL;\n-\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+\tret = bpf_copy_from_user_mm(dst, size, user_ptr, mm, flags);\n+\tif (mm)\n+\t\tmmput(mm);\n \n-\tmemset(dst, 0, size);\n-\t/* Return -EFAULT for partial read */\n-\treturn ret \u003c 0 ? ret : -EFAULT;\n+\treturn ret;\n }\n \n const struct bpf_func_proto bpf_copy_from_user_task_proto = {\n@@ -3658,6 +3655,100 @@ __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+\tint ret;\n+\n+\tif (unlikely(flags))\n+\t\treturn -EINVAL;\n+\n+\tif (unlikely(!dst__sz))\n+\t\treturn 0;\n+\n+\tif (unlikely(!mm)) {\n+\t\tmemset(dst, 0, dst__sz);\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,\n+\t\t\t dst, dst__sz, 0);\n+\tif (ret == dst__sz)\n+\t\treturn 0;\n+\n+\tmemset(dst, 0, dst__sz);\n+\treturn ret \u003c 0 ? ret : -EFAULT;\n+}\n+\n+/**\n+ * bpf_copy_from_user_mm_str() - Copy a string from an address space\n+ * @dst: Destination address, in kernel space. This buffer must be\n+ * at least @dst__sz bytes long\n+ * @dst__sz: Maximum number of bytes to copy, including the trailing NUL\n+ * @unsafe_ptr__ign: Source address in the address space\n+ * @mm: Address space to copy from\n+ * @flags: The only supported flag is BPF_F_PAD_ZEROS\n+ *\n+ * Copies a NUL-terminated string from the user address space associated with\n+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz\n+ * is zero.\n+ *\n+ * If 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+\tint ret;\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+\tif (unlikely(!mm)) {\n+\t\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\t\tmemset(dst, 0, dst__sz);\n+\t\telse\n+\t\t\t*(char *)dst = '\\0';\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);\n+\tif (ret \u003c 0) {\n+\t\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\t\tmemset(dst, 0, dst__sz);\n+\t\treturn ret;\n+\t}\n+\n+\tif (flags \u0026 BPF_F_PAD_ZEROS)\n+\t\tmemset(dst + ret, 0, dst__sz - ret);\n+\n+\treturn ret + 1;\n+}\n+\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,25 +3772,16 @@ __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-\t\treturn -EINVAL;\n-\n-\tif (unlikely(dst__sz == 0))\n-\t\treturn 0;\n+\tmm = get_task_mm(tsk);\n+\tret = bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,\n+\t\t\t\t\tmm, flags);\n+\tif (mm)\n+\t\tmmput(mm);\n \n-\tret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);\n-\tif (ret \u003c 0) {\n-\t\tif (flags \u0026 BPF_F_PAD_ZEROS)\n-\t\t\tmemset(dst, 0, dst__sz);\n-\t\treturn ret;\n-\t}\n-\n-\tif (flags \u0026 BPF_F_PAD_ZEROS)\n-\t\tmemset(dst + ret, 0, dst__sz - ret);\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 +5006,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..6799d9e080fc5 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,\n@@ -6168,6 +6173,15 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,\n \tif (ret != PTR_TO_BTF_ID) {\n \t\t/* just mark; */\n \n+\t} else if (bpf_is_trusted_or_null_btf_ptr(reg-\u003etype)) {\n+\t\t/*\n+\t\t * An unchecked load through a trusted-or-NULL pointer is\n+\t\t * fault-protected. Any pointer derived from that load must be\n+\t\t * untrusted, as a fault produces a NULL value.\n+\t\t */\n+\t\tclear_trusted_flags(\u0026flag);\n+\t\tflag |= PTR_UNTRUSTED;\n+\n \t} else if (type_flag(reg-\u003etype) \u0026 PTR_UNTRUSTED) {\n \t\t/* If this is an untrusted pointer, all pointers formed by walking it\n \t\t * also inherit the untrusted flag.\n@@ -6644,7 +6658,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b\n \t\tif (!err \u0026\u0026 t == BPF_READ \u0026\u0026 value_regno \u003e= 0)\n \t\t\tmark_reg_unknown(env, regs, value_regno);\n \t} else if (base_type(reg-\u003etype) == PTR_TO_BTF_ID \u0026\u0026\n-\t\t !type_may_be_null(reg-\u003etype)) {\n+\t\t (!type_may_be_null(reg-\u003etype) ||\n+\t\t (t == BPF_READ \u0026\u0026 bpf_is_trusted_or_null_btf_ptr(reg-\u003etype)))) {\n \t\terr = check_ptr_to_btf_access(env, regs, reg, argno, off, size, t,\n \t\t\t\t\t value_regno);\n \t} else if (reg-\u003etype == CONST_PTR_TO_MAP) {\ndiff --git a/mm/internal.h b/mm/internal.h\nindex 38b1165212c94..8264a346d18af 100644\n--- a/mm/internal.h\n+++ b/mm/internal.h\n@@ -25,6 +25,11 @@\n struct folio_batch;\n struct hstate;\n \n+#ifdef CONFIG_BPF_SYSCALL\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+#endif\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/bpf_iter.c b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c\nindex c69080ca14f5a..99a16a1add70e 100644\n--- a/tools/testing/selftests/bpf/prog_tests/bpf_iter.c\n+++ b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c\n@@ -39,10 +39,10 @@ static void test_btf_id_or_null(void)\n \tstruct bpf_iter_test_kern3 *skel;\n \n \tskel = bpf_iter_test_kern3__open_and_load();\n-\tif (!ASSERT_ERR_PTR(skel, \"bpf_iter_test_kern3__open_and_load\")) {\n-\t\tbpf_iter_test_kern3__destroy(skel);\n+\tif (!ASSERT_OK_PTR(skel, \"bpf_iter_test_kern3__open_and_load\"))\n \t\treturn;\n-\t}\n+\n+\tbpf_iter_test_kern3__destroy(skel);\n }\n \n static void do_dummy_read_opts(struct bpf_program *prog, struct bpf_iter_attach_opts *opts)\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/prog_tests/test_struct_ops_maybe_null.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c\nindex 01dc2613c8a51..45af6f00ad908 100644\n--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c\n+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c\n@@ -19,15 +19,16 @@ static void maybe_null(void)\n \tstruct_ops_maybe_null__destroy(skel);\n }\n \n-/* Test that the verifier rejects a program that access a nullable pointer\n- * without a check beforehand.\n+/*\n+ * Test that the verifier accepts a fault-protected read through a nullable\n+ * trusted pointer without an explicit NULL check.\n */\n-static void maybe_null_fail(void)\n+static void maybe_null_no_check(void)\n {\n \tstruct struct_ops_maybe_null_fail *skel;\n \n \tskel = struct_ops_maybe_null_fail__open_and_load();\n-\tif (ASSERT_ERR_PTR(skel, \"struct_ops_module_fail__open_and_load\"))\n+\tif (!ASSERT_OK_PTR(skel, \"struct_ops_maybe_null_fail__open_and_load\"))\n \t\treturn;\n \n \tstruct_ops_maybe_null_fail__destroy(skel);\n@@ -41,6 +42,6 @@ void test_struct_ops_maybe_null(void)\n \t */\n \tif (test__start_subtest(\"maybe_null\"))\n \t\tmaybe_null();\n-\tif (test__start_subtest(\"maybe_null_fail\"))\n-\t\tmaybe_null_fail();\n+\tif (test__start_subtest(\"maybe_null_no_check\"))\n+\t\tmaybe_null_no_check();\n }\ndiff --git a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c\nindex accc42e01f8a8..825fe7a92d74f 100644\n--- a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c\n+++ b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c\n@@ -2,6 +2,31 @@\n \n #include \u003ctest_progs.h\u003e\n #include \"test_tp_btf_nullable.skel.h\"\n+#include \"test_tp_btf_nullable_runtime.skel.h\"\n+\n+static void test_nullable_runtime(void)\n+{\n+\tstruct test_tp_btf_nullable_runtime *skel;\n+\n+\tskel = test_tp_btf_nullable_runtime__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"open_and_load\"))\n+\t\treturn;\n+\n+\tskel-\u003ebss-\u003emonitored_tid = sys_gettid();\n+\n+\tif (!ASSERT_OK(test_tp_btf_nullable_runtime__attach(skel), \"attach\"))\n+\t\tgoto out;\n+\n+\tif (!ASSERT_OK(trigger_module_test_read(2), \"trigger\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(skel-\u003ebss-\u003ecalls, 2, \"calls\");\n+\tASSERT_EQ(skel-\u003ebss-\u003enonnull_len, 2, \"nonnull_len\");\n+\tASSERT_EQ(skel-\u003ebss-\u003enull_len, 0, \"null_len\");\n+\n+out:\n+\ttest_tp_btf_nullable_runtime__destroy(skel);\n+}\n \n void test_tp_btf_nullable(void)\n {\n@@ -11,4 +36,7 @@ void test_tp_btf_nullable(void)\n \t}\n \n \tRUN_TESTS(test_tp_btf_nullable);\n+\n+\tif (test__start_subtest(\"runtime\"))\n+\t\ttest_nullable_runtime();\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/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c\nindex 725d73c9ffe13..163124793d0a6 100644\n--- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c\n+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c\n@@ -2,22 +2,34 @@\n /* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */\n \n #include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n #include \u003cbpf/bpf_tracing.h\u003e\n #include \"bpf_misc.h\"\n \n char _license[] SEC(\"license\") = \"GPL\";\n \n-/* Ensure module parameter has PTR_MAYBE_NULL */\n+extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;\n+extern void bpf_task_release(struct task_struct *p) __ksym;\n+\n+/*\n+ * Ensure the module tracepoint argument is trusted-or-NULL while allowing\n+ * a fault-protected read without an explicit NULL check.\n+ */\n SEC(\"tp_btf/bpf_testmod_test_raw_tp_null_tp\")\n-__failure __msg(\"R1 invalid mem access 'trusted_ptr_or_null_'\")\n+__success __log_level(2)\n+__msg(\"R1=trusted_ptr_or_null_sk_buff\")\n int test_raw_tp_null_bpf_testmod_test_raw_tp_null_arg_1(void *ctx) {\n asm volatile(\"r1 = *(u64 *)(r1 +0); r1 = *(u64 *)(r1 +0);\" ::: __clobber_all);\n return 0;\n }\n \n-/* Check NULL marking */\n+/*\n+ * Ensure sched_pi_setprio's second argument is trusted-or-NULL while allowing\n+ * a fault-protected read without an explicit NULL check.\n+ */\n SEC(\"tp_btf/sched_pi_setprio\")\n-__failure __msg(\"R1 invalid mem access 'trusted_ptr_or_null_'\")\n+__success __log_level(2)\n+__msg(\"R1=trusted_ptr_or_null_task_struct\")\n int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) {\n asm volatile(\"r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);\" ::: __clobber_all);\n return 0;\n@@ -60,7 +72,8 @@ int test_tp_btf_signal_deliver_info_no_deref(void *ctx)\n }\n \n SEC(\"tp_btf/sched_process_wait\")\n-__failure __msg(\"R1 invalid mem access 'trusted_ptr_or_null_'\")\n+__success __log_level(2)\n+__msg(\"R1=trusted_ptr_or_null_pid\")\n int test_raw_tp_null_sched_process_wait_arg_1(void *ctx)\n {\n \tasm volatile(\"r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);\" ::: __clobber_all);\n@@ -75,3 +88,58 @@ int test_raw_tp_null_sched_process_wait_arg_1_checked(void *ctx)\n \t\t \"r1 = *(u32 *)(r1 +0);\" ::: __clobber_all);\n \treturn 0;\n }\n+\n+SEC(\"tp_btf/sched_pi_setprio\")\n+__failure __log_level(2)\n+__msg(\"R1=untrusted_ptr_task_struct\")\n+__msg(\"R1 must be a rcu pointer\")\n+int BPF_PROG(trusted_or_null_walk_is_untrusted, struct task_struct *task,\n+\t struct task_struct *pi_task)\n+{\n+\tstruct task_struct *parent, *acquired;\n+\n+\tparent = pi_task-\u003ereal_parent;\n+\tacquired = bpf_task_acquire(parent);\n+\tif (acquired)\n+\t\tbpf_task_release(acquired);\n+\treturn 0;\n+}\n+\n+SEC(\"tp_btf/sched_pi_setprio\")\n+__failure __msg(\"R1 must be a rcu pointer\")\n+int BPF_PROG(derived_ptr_null_check_does_not_restore_trust,\n+\t struct task_struct *task, struct task_struct *pi_task)\n+{\n+\tstruct task_struct *parent, *acquired;\n+\n+\tparent = pi_task-\u003ereal_parent;\n+\tif (!parent)\n+\t\treturn 0;\n+\n+\tacquired = bpf_task_acquire(parent);\n+\tif (acquired)\n+\t\tbpf_task_release(acquired);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * In contrast, checking the original trusted-or-NULL pointer removes\n+ * PTR_MAYBE_NULL while retaining PTR_TRUSTED.\n+ */\n+SEC(\"tp_btf/sched_pi_setprio\")\n+__success\n+int BPF_PROG(original_ptr_null_check_retains_trust,\n+\t struct task_struct *task, struct task_struct *pi_task)\n+{\n+\tstruct task_struct *acquired;\n+\n+\tif (!pi_task)\n+\t\treturn 0;\n+\n+\tacquired = bpf_task_acquire(pi_task);\n+\tif (acquired)\n+\t\tbpf_task_release(acquired);\n+\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c\nindex cf0547a613ffc..b7914224ba194 100644\n--- a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c\n+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c\n@@ -7,7 +7,7 @@\n #include \"bpf_misc.h\"\n \n SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n-__failure __msg(\"R1 invalid mem access 'trusted_ptr_or_null_'\")\n+__success\n int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx)\n {\n \treturn nullable_ctx-\u003elen;\n@@ -21,4 +21,47 @@ int BPF_PROG(handle_tp_btf_nullable_bare2, struct bpf_testmod_test_read_ctx *nul\n \treturn 0;\n }\n \n+SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n+__success\n+int BPF_PROG(handle_tp_btf_nullable_mem, struct bpf_testmod_test_read_ctx *nullable_ctx)\n+{\n+\treturn nullable_ctx-\u003ebuf[0];\n+}\n+\n+SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n+__failure __msg(\"pointer arithmetic on trusted_ptr_or_null_ prohibited\")\n+int BPF_PROG(handle_tp_btf_nullable_arith, struct bpf_testmod_test_read_ctx *nullable_ctx)\n+{\n+\tasm volatile(\"%[ctx] += 1\" : [ctx] \"+r\"(nullable_ctx));\n+\treturn nullable_ctx-\u003elen;\n+}\n+\n+SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n+__failure __msg(\"invalid mem access 'trusted_ptr_or_null_'\")\n+int BPF_PROG(handle_tp_btf_nullable_atomic_rmw,\n+\t struct bpf_testmod_test_read_ctx *nullable_ctx)\n+{\n+\tasm volatile (\"r1 = %[ctx];\"\n+\t\t \"w2 = 1;\"\n+\t\t \"lock *(u32 *)(r1 + %[len]) += w2;\"\n+\t\t :\n+\t\t : [ctx] \"r\"(nullable_ctx),\n+\t\t\t__imm_const(len,\n+\t\t\t\t offsetof(struct bpf_testmod_test_read_ctx,\n+\t\t\t\t\t len))\n+\t\t : \"r1\", \"r2\", \"memory\");\n+\treturn 0;\n+}\n+\n+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL\n+SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n+__failure\n+__msg(\"BPF_ATOMIC loads from R{{[0-9]+}} trusted_ptr_or_null_\")\n+int BPF_PROG(handle_tp_btf_nullable_load_acquire,\n+\t struct bpf_testmod_test_read_ctx *nullable_ctx)\n+{\n+\treturn __atomic_load_n(\u0026nullable_ctx-\u003elen, __ATOMIC_ACQUIRE);\n+}\n+#endif\n+\n char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c\nnew file mode 100644\nindex 0000000000000..5c9c7f94040dc\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c\n@@ -0,0 +1,35 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"../test_kmods/bpf_testmod.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+int monitored_tid;\n+int calls;\n+__u64 nonnull_len;\n+__u64 null_len;\n+\n+SEC(\"tp_btf/bpf_testmod_test_nullable_bare_tp\")\n+int BPF_PROG(handle_nullable_runtime,\n+\t struct bpf_testmod_test_read_ctx *nullable_ctx)\n+{\n+\t__u32 tid = bpf_get_current_pid_tgid();\n+\t__u64 len;\n+\tint call;\n+\n+\tif (tid != monitored_tid)\n+\t\treturn 0;\n+\n+\tlen = nullable_ctx-\u003elen;\n+\tcall = calls++;\n+\n+\tif (call == 0)\n+\t\tnonnull_len = len;\n+\telse if (call == 1)\n+\t\tnull_len = len;\n+\n+\treturn 0;\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c\nindex c724bf389f5c6..fac133d90f5e6 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c\n@@ -162,13 +162,13 @@ __naked int disabled_hook_test3(void *ctx)\n \n SEC(\"lsm/mmap_file\")\n __description(\"not null checking nullable pointer in bpf_lsm_mmap_file\")\n-__failure __msg(\"R1 invalid mem access 'trusted_ptr_or_null_'\")\n+__success\n int BPF_PROG(no_null_check, struct file *file)\n {\n-\tstruct inode *inode;\n+\tino_t ino;\n \n-\tinode = file-\u003ef_inode;\n-\t__sink(inode);\n+\tino = file-\u003ef_inode-\u003ei_ino;\n+\t__sink(ino);\n \n \treturn 0;\n }\n@@ -188,6 +188,16 @@ int BPF_PROG(null_check, struct file *file)\n \treturn 0;\n }\n \n+SEC(\"lsm/mmap_file\")\n+__description(\"store through trusted-or-null file is rejected\")\n+__failure\n+__msg(\"R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'\")\n+int BPF_PROG(store_through_trusted_or_null_file, struct file *file)\n+{\n+\tfile-\u003ef_flags = 0;\n+\treturn 0;\n+}\n+\n SEC(\"lsm_cgroup/file_open\")\n __description(\"sleepable lsm_cgroup program is rejected\")\n __failure __msg(\"Program of this type cannot be sleepable\")\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c\nindex 55398c04290a8..17c1542cc7e17 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c\n@@ -100,4 +100,18 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,\n \treturn 0;\n }\n \n+SEC(\"lsm.s/inode_rename\")\n+__success __log_level(2)\n+__msg(\"R{{[0-9]+}}=trusted_ptr_or_null_inode\")\n+int BPF_PROG(inode_rename_no_null_check, struct inode *old_dir,\n+\t struct dentry *old_dentry, struct inode *new_dir,\n+\t struct dentry *new_dentry, unsigned int flags)\n+{\n+\tino_t ino = new_dentry-\u003ed_inode-\u003ei_ino;\n+\n+\tif (ino == 0)\n+\t\treturn -EACCES;\n+\treturn 0;\n+}\n+\n char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\nindex 8f0c45421f893..2a0813258183e 100644\n--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\n+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c\n@@ -159,18 +159,4 @@ int BPF_PROG(path_d_path_kfunc_non_lsm, struct path *path, struct file *f)\n \treturn 0;\n }\n \n-SEC(\"lsm.s/inode_rename\")\n-__failure __msg(\"invalid mem access 'trusted_ptr_or_null_'\")\n-int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,\n-\t struct inode *new_dir, struct dentry *new_dentry,\n-\t unsigned int flags)\n-{\n-\tstruct inode *inode = new_dentry-\u003ed_inode;\n-\tino_t ino;\n-\n-\tino = inode-\u003ei_ino;\n-\tif (ino == 0)\n-\t\treturn -EACCES;\n-\treturn 0;\n-}\n char _license[] SEC(\"license\") = \"GPL\";\ndiff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\nindex f798bbbb4d13c..2ba6a83d243f5 100644\n--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c\n@@ -702,6 +702,7 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,\n \tif (bpf_testmod_loop_test(101) \u003e 100)\n \t\ttrace_bpf_testmod_test_read(current, \u0026ctx);\n \n+\ttrace_bpf_testmod_test_nullable_bare_tp(\u0026ctx);\n \ttrace_bpf_testmod_test_nullable_bare_tp(NULL);\n \n \t/* Magic number to enable writable tp */\ndiff --git a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c\nindex ec724d7b33d18..ecaa36355caec 100644\n--- a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c\n+++ b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c\n@@ -7,14 +7,13 @@\n \n char _license[] SEC(\"license\") = \"GPL\";\n \n-u64 vtime_test;\n-\n void BPF_STRUCT_OPS(maybe_null_running, struct task_struct *p)\n {}\n \n void BPF_STRUCT_OPS(maybe_null_fail_dispatch, s32 cpu, struct task_struct *p)\n {\n-\tvtime_test = p-\u003escx.dsq_vtime;\n+\t/* Pointer arithmetic on a trusted-or-NULL pointer must be rejected. */\n+\tasm volatile(\"%[p] += 0\" : [p] \"+r\"(p));\n }\n \n SEC(\".struct_ops.link\")\n"
} |
| 2/1 | 2026/09/07 20:20 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF",
"BPF_LSM"
],
"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",
"check_ptr_to_btf_access",
"type_is_trusted_or_null",
"check_mem_access",
"bpf_convert_ctx_accesses",
"free_bprm"
],
"Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm and bpf_copy_from_user_mm_str), refactors remote MM string copying helpers across mm/ and kernel/bpf/, modifies the BPF verifier to allow fault-protected loads through trusted-or-null pointers, and alters mm cleanup in free_bprm(). These changes touch core BPF and memory management execution paths reachable in standard QEMU environments via BPF and exec syscalls.",
"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 db77af66b3aa07b13dc83aafca1b8b0a0291a1fb
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Sep 7 20:20:53 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/bpf_verifier.h b/include/linux/bpf_verifier.h
index 9727df5af83ab..4f032ad83c675 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1339,6 +1339,11 @@ static inline bool bpf_is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
}
}
+static inline bool bpf_is_trusted_or_null_btf_ptr(enum bpf_reg_type type)
+{
+ return type == (PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL);
+}
+
static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
{
/*
@@ -1346,7 +1351,9 @@ static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
* protection, that is, the ones bpf_convert_ctx_accesses() has to
* turn a BPF_LDX into a BPF_PROBE_MEM one for.
*/
- return type == PTR_TO_BTF_ID || (type_flag(type) & PTR_UNTRUSTED);
+ return type == PTR_TO_BTF_ID ||
+ (type_flag(type) & PTR_UNTRUSTED) ||
+ bpf_is_trusted_or_null_btf_ptr(type);
}
static inline bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23e..d5bde1f71a97e 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3326,6 +3326,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
#ifdef CONFIG_BPF_SYSCALL
+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
#endif
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..d3c564437ad06 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -32,6 +32,10 @@
#include "../../lib/kstrtox.h"
+__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 kernel subsystem is allowing eBPF programs to call this function,
* inside its own verifier_ops->get_func_proto() callback it should return
* bpf_map_lookup_elem_proto, so that verifier can properly check the arguments
@@ -682,22 +686,15 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
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 */
- if (unlikely(flags))
- return -EINVAL;
-
- 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);
+ ret = bpf_copy_from_user_mm(dst, size, user_ptr, mm, flags);
+ if (mm)
+ mmput(mm);
- memset(dst, 0, size);
- /* Return -EFAULT for partial read */
- return ret < 0 ? ret : -EFAULT;
+ return ret;
}
const struct bpf_func_proto bpf_copy_from_user_task_proto = {
@@ -3658,6 +3655,100 @@ __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)
+{
+ int ret;
+
+ if (unlikely(flags))
+ return -EINVAL;
+
+ if (unlikely(!dst__sz))
+ return 0;
+
+ if (unlikely(!mm)) {
+ memset(dst, 0, dst__sz);
+ return -EFAULT;
+ }
+
+ ret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,
+ dst, dst__sz, 0);
+ if (ret == dst__sz)
+ return 0;
+
+ memset(dst, 0, dst__sz);
+ return ret < 0 ? ret : -EFAULT;
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst: Destination address, in kernel space. This buffer must be
+ * at least @dst__sz bytes long
+ * @dst__sz: Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If 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)
+{
+ int ret;
+
+ if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+ return -EINVAL;
+
+ if (unlikely(dst__sz == 0))
+ return 0;
+
+ if (unlikely(!mm)) {
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, dst__sz);
+ else
+ *(char *)dst = '\0';
+ return -EFAULT;
+ }
+
+ ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
+ if (ret < 0) {
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, dst__sz);
+ return ret;
+ }
+
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst + ret, 0, dst__sz - ret);
+
+ return ret + 1;
+}
+
/**
* 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,25 +3772,16 @@ __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))
- return -EINVAL;
-
- if (unlikely(dst__sz == 0))
- return 0;
+ mm = get_task_mm(tsk);
+ ret = bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+ mm, flags);
+ if (mm)
+ mmput(mm);
- ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
- if (ret < 0) {
- if (flags & BPF_F_PAD_ZEROS)
- memset(dst, 0, dst__sz);
- return ret;
- }
-
- if (flags & BPF_F_PAD_ZEROS)
- memset(dst + ret, 0, dst__sz - ret);
-
- return ret + 1;
+ return ret;
}
/* Keep unsigned long in prototype so that kfunc is usable when emitted to
@@ -4924,6 +5006,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..6799d9e080fc5 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,
@@ -6168,6 +6173,15 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
if (ret != PTR_TO_BTF_ID) {
/* just mark; */
+ } else if (bpf_is_trusted_or_null_btf_ptr(reg->type)) {
+ /*
+ * An unchecked load through a trusted-or-NULL pointer is
+ * fault-protected. Any pointer derived from that load must be
+ * untrusted, as a fault produces a NULL value.
+ */
+ clear_trusted_flags(&flag);
+ flag |= PTR_UNTRUSTED;
+
} else if (type_flag(reg->type) & PTR_UNTRUSTED) {
/* If this is an untrusted pointer, all pointers formed by walking it
* also inherit the untrusted flag.
@@ -6644,7 +6658,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else if (base_type(reg->type) == PTR_TO_BTF_ID &&
- !type_may_be_null(reg->type)) {
+ (!type_may_be_null(reg->type) ||
+ (t == BPF_READ && bpf_is_trusted_or_null_btf_ptr(reg->type)))) {
err = check_ptr_to_btf_access(env, regs, reg, argno, off, size, t,
value_regno);
} else if (reg->type == CONST_PTR_TO_MAP) {
diff --git a/mm/internal.h b/mm/internal.h
index 38b1165212c94..8264a346d18af 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -25,6 +25,11 @@
struct folio_batch;
struct hstate;
+#ifdef CONFIG_BPF_SYSCALL
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
+#endif
+
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/bpf_iter.c b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
index c69080ca14f5a..99a16a1add70e 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
@@ -39,10 +39,10 @@ static void test_btf_id_or_null(void)
struct bpf_iter_test_kern3 *skel;
skel = bpf_iter_test_kern3__open_and_load();
- if (!ASSERT_ERR_PTR(skel, "bpf_iter_test_kern3__open_and_load")) {
- bpf_iter_test_kern3__destroy(skel);
+ if (!ASSERT_OK_PTR(skel, "bpf_iter_test_kern3__open_and_load"))
return;
- }
+
+ bpf_iter_test_kern3__destroy(skel);
}
static void do_dummy_read_opts(struct bpf_program *prog, struct bpf_iter_attach_opts *opts)
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/prog_tests/test_struct_ops_maybe_null.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
index 01dc2613c8a51..45af6f00ad908 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
@@ -19,15 +19,16 @@ static void maybe_null(void)
struct_ops_maybe_null__destroy(skel);
}
-/* Test that the verifier rejects a program that access a nullable pointer
- * without a check beforehand.
+/*
+ * Test that the verifier accepts a fault-protected read through a nullable
+ * trusted pointer without an explicit NULL check.
*/
-static void maybe_null_fail(void)
+static void maybe_null_no_check(void)
{
struct struct_ops_maybe_null_fail *skel;
skel = struct_ops_maybe_null_fail__open_and_load();
- if (ASSERT_ERR_PTR(skel, "struct_ops_module_fail__open_and_load"))
+ if (!ASSERT_OK_PTR(skel, "struct_ops_maybe_null_fail__open_and_load"))
return;
struct_ops_maybe_null_fail__destroy(skel);
@@ -41,6 +42,6 @@ void test_struct_ops_maybe_null(void)
*/
if (test__start_subtest("maybe_null"))
maybe_null();
- if (test__start_subtest("maybe_null_fail"))
- maybe_null_fail();
+ if (test__start_subtest("maybe_null_no_check"))
+ maybe_null_no_check();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
index accc42e01f8a8..825fe7a92d74f 100644
--- a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
+++ b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
@@ -2,6 +2,31 @@
#include <test_progs.h>
#include "test_tp_btf_nullable.skel.h"
+#include "test_tp_btf_nullable_runtime.skel.h"
+
+static void test_nullable_runtime(void)
+{
+ struct test_tp_btf_nullable_runtime *skel;
+
+ skel = test_tp_btf_nullable_runtime__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ skel->bss->monitored_tid = sys_gettid();
+
+ if (!ASSERT_OK(test_tp_btf_nullable_runtime__attach(skel), "attach"))
+ goto out;
+
+ if (!ASSERT_OK(trigger_module_test_read(2), "trigger"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->calls, 2, "calls");
+ ASSERT_EQ(skel->bss->nonnull_len, 2, "nonnull_len");
+ ASSERT_EQ(skel->bss->null_len, 0, "null_len");
+
+out:
+ test_tp_btf_nullable_runtime__destroy(skel);
+}
void test_tp_btf_nullable(void)
{
@@ -11,4 +36,7 @@ void test_tp_btf_nullable(void)
}
RUN_TESTS(test_tp_btf_nullable);
+
+ if (test__start_subtest("runtime"))
+ test_nullable_runtime();
}
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/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
index 725d73c9ffe13..163124793d0a6 100644
--- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -2,22 +2,34 @@
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
-/* Ensure module parameter has PTR_MAYBE_NULL */
+extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
+extern void bpf_task_release(struct task_struct *p) __ksym;
+
+/*
+ * Ensure the module tracepoint argument is trusted-or-NULL while allowing
+ * a fault-protected read without an explicit NULL check.
+ */
SEC("tp_btf/bpf_testmod_test_raw_tp_null_tp")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_sk_buff")
int test_raw_tp_null_bpf_testmod_test_raw_tp_null_arg_1(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
}
-/* Check NULL marking */
+/*
+ * Ensure sched_pi_setprio's second argument is trusted-or-NULL while allowing
+ * a fault-protected read without an explicit NULL check.
+ */
SEC("tp_btf/sched_pi_setprio")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_task_struct")
int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
@@ -60,7 +72,8 @@ int test_tp_btf_signal_deliver_info_no_deref(void *ctx)
}
SEC("tp_btf/sched_process_wait")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_pid")
int test_raw_tp_null_sched_process_wait_arg_1(void *ctx)
{
asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
@@ -75,3 +88,58 @@ int test_raw_tp_null_sched_process_wait_arg_1_checked(void *ctx)
"r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
return 0;
}
+
+SEC("tp_btf/sched_pi_setprio")
+__failure __log_level(2)
+__msg("R1=untrusted_ptr_task_struct")
+__msg("R1 must be a rcu pointer")
+int BPF_PROG(trusted_or_null_walk_is_untrusted, struct task_struct *task,
+ struct task_struct *pi_task)
+{
+ struct task_struct *parent, *acquired;
+
+ parent = pi_task->real_parent;
+ acquired = bpf_task_acquire(parent);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("tp_btf/sched_pi_setprio")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(derived_ptr_null_check_does_not_restore_trust,
+ struct task_struct *task, struct task_struct *pi_task)
+{
+ struct task_struct *parent, *acquired;
+
+ parent = pi_task->real_parent;
+ if (!parent)
+ return 0;
+
+ acquired = bpf_task_acquire(parent);
+ if (acquired)
+ bpf_task_release(acquired);
+
+ return 0;
+}
+
+/*
+ * In contrast, checking the original trusted-or-NULL pointer removes
+ * PTR_MAYBE_NULL while retaining PTR_TRUSTED.
+ */
+SEC("tp_btf/sched_pi_setprio")
+__success
+int BPF_PROG(original_ptr_null_check_retains_trust,
+ struct task_struct *task, struct task_struct *pi_task)
+{
+ struct task_struct *acquired;
+
+ if (!pi_task)
+ return 0;
+
+ acquired = bpf_task_acquire(pi_task);
+ if (acquired)
+ bpf_task_release(acquired);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
index cf0547a613ffc..b7914224ba194 100644
--- a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
@@ -7,7 +7,7 @@
#include "bpf_misc.h"
SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx)
{
return nullable_ctx->len;
@@ -21,4 +21,47 @@ int BPF_PROG(handle_tp_btf_nullable_bare2, struct bpf_testmod_test_read_ctx *nul
return 0;
}
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__success
+int BPF_PROG(handle_tp_btf_nullable_mem, struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ return nullable_ctx->buf[0];
+}
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure __msg("pointer arithmetic on trusted_ptr_or_null_ prohibited")
+int BPF_PROG(handle_tp_btf_nullable_arith, struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ asm volatile("%[ctx] += 1" : [ctx] "+r"(nullable_ctx));
+ return nullable_ctx->len;
+}
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(handle_tp_btf_nullable_atomic_rmw,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ asm volatile ("r1 = %[ctx];"
+ "w2 = 1;"
+ "lock *(u32 *)(r1 + %[len]) += w2;"
+ :
+ : [ctx] "r"(nullable_ctx),
+ __imm_const(len,
+ offsetof(struct bpf_testmod_test_read_ctx,
+ len))
+ : "r1", "r2", "memory");
+ return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure
+__msg("BPF_ATOMIC loads from R{{[0-9]+}} trusted_ptr_or_null_")
+int BPF_PROG(handle_tp_btf_nullable_load_acquire,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ return __atomic_load_n(&nullable_ctx->len, __ATOMIC_ACQUIRE);
+}
+#endif
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c
new file mode 100644
index 0000000000000..5c9c7f94040dc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+int monitored_tid;
+int calls;
+__u64 nonnull_len;
+__u64 null_len;
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+int BPF_PROG(handle_nullable_runtime,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ __u32 tid = bpf_get_current_pid_tgid();
+ __u64 len;
+ int call;
+
+ if (tid != monitored_tid)
+ return 0;
+
+ len = nullable_ctx->len;
+ call = calls++;
+
+ if (call == 0)
+ nonnull_len = len;
+ else if (call == 1)
+ null_len = len;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index c724bf389f5c6..fac133d90f5e6 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -162,13 +162,13 @@ __naked int disabled_hook_test3(void *ctx)
SEC("lsm/mmap_file")
__description("not null checking nullable pointer in bpf_lsm_mmap_file")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
int BPF_PROG(no_null_check, struct file *file)
{
- struct inode *inode;
+ ino_t ino;
- inode = file->f_inode;
- __sink(inode);
+ ino = file->f_inode->i_ino;
+ __sink(ino);
return 0;
}
@@ -188,6 +188,16 @@ int BPF_PROG(null_check, struct file *file)
return 0;
}
+SEC("lsm/mmap_file")
+__description("store through trusted-or-null file is rejected")
+__failure
+__msg("R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(store_through_trusted_or_null_file, struct file *file)
+{
+ file->f_flags = 0;
+ return 0;
+}
+
SEC("lsm_cgroup/file_open")
__description("sleepable lsm_cgroup program is rejected")
__failure __msg("Program of this type cannot be sleepable")
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
index 55398c04290a8..17c1542cc7e17 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
@@ -100,4 +100,18 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
return 0;
}
+SEC("lsm.s/inode_rename")
+__success __log_level(2)
+__msg("R{{[0-9]+}}=trusted_ptr_or_null_inode")
+int BPF_PROG(inode_rename_no_null_check, struct inode *old_dir,
+ struct dentry *old_dentry, struct inode *new_dir,
+ struct dentry *new_dentry, unsigned int flags)
+{
+ ino_t ino = new_dentry->d_inode->i_ino;
+
+ if (ino == 0)
+ return -EACCES;
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index 8f0c45421f893..2a0813258183e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -159,18 +159,4 @@ int BPF_PROG(path_d_path_kfunc_non_lsm, struct path *path, struct file *f)
return 0;
}
-SEC("lsm.s/inode_rename")
-__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
-int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry,
- unsigned int flags)
-{
- struct inode *inode = new_dentry->d_inode;
- ino_t ino;
-
- ino = inode->i_ino;
- if (ino == 0)
- return -EACCES;
- return 0;
-}
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index f798bbbb4d13c..2ba6a83d243f5 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -702,6 +702,7 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
if (bpf_testmod_loop_test(101) > 100)
trace_bpf_testmod_test_read(current, &ctx);
+ trace_bpf_testmod_test_nullable_bare_tp(&ctx);
trace_bpf_testmod_test_nullable_bare_tp(NULL);
/* Magic number to enable writable tp */
diff --git a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
index ec724d7b33d18..ecaa36355caec 100644
--- a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
+++ b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
@@ -7,14 +7,13 @@
char _license[] SEC("license") = "GPL";
-u64 vtime_test;
-
void BPF_STRUCT_OPS(maybe_null_running, struct task_struct *p)
{}
void BPF_STRUCT_OPS(maybe_null_fail_dispatch, s32 cpu, struct task_struct *p)
{
- vtime_test = p->scx.dsq_vtime;
+ /* Pointer arithmetic on a trusted-or-NULL pointer must be rejected. */
+ asm volatile("%[p] += 0" : [p] "+r"(p));
}
SEC(".struct_ops.link")
|
| 3/2 | 2026/09/07 20:20 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 17994 output: 21 thoughts: 1137 Thoughts: **Analysis of the Patch for Syzkaller Fuzzing Potential** Okay, let's break this down. My initial assessment is that this patch *does* warrant further investigation for fuzzing potential with syzkaller. The code touches several key areas of the kernel, and the changes are significant enough to warrant a focused approach. First, I see modifications in `fs/exec.c`, specifically `free_bprm`. While it looks like a small change, setting `bprm->mm = NULL` before `mmput(mm)` could potentially introduce subtle memory management issues that fuzzing could uncover. Then, the second section, encompassing `include/linux/bpf_verifier.h` and `kernel/bpf/verifier.c`, is particularly interesting. The introduction of `bpf_is_trusted_or_null_btf_ptr` and the related modifications to `bpf_may_fault_on_deref` within the BPF verifier are high-value targets. This indicates new logic for handling potentially untrusted pointers, which is a prime area for fuzzing. The changes around `BTF_TYPE_SAFE_TRUSTED_OR_NULL` and the alterations in `check_ptr_to_btf_access` and `check_mem_access` point to potential new BPF features and vulnerabilities. The addition of new kfuncs like `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, and the refactoring of existing functions to use them (`bpf_copy_from_user_task`, `bpf_copy_from_user_task_str`) are also noteworthy. The fact that these kfuncs are registered as `KF_SLEEPABLE` suggests a likely new set of features or enhancements for bpf programs. The changes to how data is copied from user space *through* the mm layer, and the potential for races introduced by the use of sleepable kfuncs, is significant for security. The changes in `mm/internal.h`, `mm/memory.c`, `mm/nommu.c`, and `mm/util.c` are also important. The introduction of `copy_remote_mm_str` (previously `__copy_remote_vm_str`) and its interactions with `get_task_mm(tsk)` indicate modifications related to remote memory manipulation. This could have implications for memory corruption, races, or use-after-free bugs. The existence of tests in `tools/testing/selftests/...` adds further weight to the idea that this is significant code. The tests may not be exhaustive, so a carefully crafted fuzzing campaign is worthwhile. The fact that all this code is reachable in standard QEMU/amd64 setups, via the BPF syscall (eBPF programs, LSM hooks, tracepoints, syscalls, etc.) and `execve`, is the key factor. The core BPF verifier, the BPF helpers/kfuncs, the mm functions, and even `free_bprm` are all within the attack surface. Based on the criteria for "WorthFuzzing=true," this patch definitely meets the requirements. It touches core subsystems (BPF, mm), includes refactorings, and introduces new logic within reachable code paths. Now, I'll need to dig into the Kconfig options. I need to make sure I understand the dependencies for BPF syscall, the BPF verifier, the new kfuncs, and everything else in this patch. I'll need to use grepper or source inspection to determine all relevant config options. Then I'll identify the critical focus symbols to feed syzkaller. |
| 4/2 | 2026/09/07 20:21 | tool | grepper |
0mArgs:{
"Expression": "config BPF_SYSCALL"
}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"
} |
| 5/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 20017 output: 34 thoughts: 29 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Analyzing BPF_SYSCALL Dependencies in Kconfig** Alright, let's dive in. My immediate thought is to zero in on the `kernel/bpf/Kconfig` file, specifically around line 31. That's where I'd expect to find the conditional logic and dependencies for the `BPF_SYSCALL` option. I need to understand what other Kconfig symbols, and thus configurations, must be enabled for BPF syscall support to be compiled into the kernel. This isn't just about the direct dependencies, but also the indirectly implied ones. I'm looking for clues about things like architecture-specific requirements (like whether the architecture provides the needed infrastructure), security features, or even other BPF-related configurations that might be a prerequisite. Knowing this is crucial for debugging build problems, tailoring a custom kernel configuration, or even predicting potential compatibility issues. I'm expecting to see `depends on` statements linking this symbol to specific hardware features or other modules. Time to confirm and refine my mental model of what needs to be in place. |
| 6/2 | 2026/09/07 20:21 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Kconfig",
"FirstLine": 30,
"LineCount": 40
}Results: {
"Contents": " 30:\t\n 31:\tconfig BPF_SYSCALL\n 32:\t\tbool \"Enable bpf() system call\"\n 33:\t\tselect BPF\n 34:\t\tselect IRQ_WORK\n 35:\t\tselect NEED_TASKS_RCU\n 36:\t\tselect TASKS_TRACE_RCU\n 37:\t\tselect BINARY_PRINTF\n 38:\t\tselect NET_SOCK_MSG if NET\n 39:\t\tselect NET_XGRESS if NET\n 40:\t\tselect PAGE_POOL if NET\n 41:\t\tdefault n\n 42:\t\thelp\n 43:\t\t Enable the bpf() system call that allows to manipulate BPF programs\n 44:\t\t and maps via file descriptors.\n 45:\t\n 46:\tconfig BPF_JIT\n 47:\t\tbool \"Enable BPF Just In Time compiler\"\n 48:\t\tdepends on BPF\n 49:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n 50:\t\tselect EXECMEM\n 51:\t\thelp\n 52:\t\t BPF programs are normally handled by a BPF interpreter. This option\n 53:\t\t allows the kernel to generate native code when a program is loaded\n 54:\t\t into the kernel. This will significantly speed-up processing of BPF\n 55:\t\t programs.\n 56:\t\n 57:\t\t Note, an admin should enable this feature changing:\n 58:\t\t /proc/sys/net/core/bpf_jit_enable\n 59:\t\t /proc/sys/net/core/bpf_jit_harden (optional)\n 60:\t\t /proc/sys/net/core/bpf_jit_kallsyms (optional)\n 61:\t\n 62:\tconfig BPF_JIT_ALWAYS_ON\n 63:\t\tbool \"Permanently enable BPF JIT and remove BPF interpreter\"\n 64:\t\tdepends on BPF_SYSCALL \u0026\u0026 HAVE_EBPF_JIT \u0026\u0026 BPF_JIT\n 65:\t\thelp\n 66:\t\t Enables BPF JIT and removes BPF interpreter to avoid speculative\n 67:\t\t execution of BPF instructions by the interpreter.\n 68:\t\n 69:\t\t When CONFIG_BPF_JIT_ALWAYS_ON is enabled, /proc/sys/net/core/bpf_jit_enable\n"
} |
| 7/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 20928 output: 21 thoughts: 34 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Debugging BTF Configuration** Alright, given that we're dealing with both BTF, and the potential for kfuncs and/or trusted pointers, my immediate thought is to zero in on how `DEBUG_INFO_BTF` is configured. We're obviously relying on BTF data for some critical functionality here, whether it's symbol resolution, type information, or potentially validation related to these more advanced features. So, the first thing I need to do is verify the state of BTF. Essentially, I'm thinking, "Let's check `DEBUG_INFO_BTF` and verify its current configuration for these BTF dependencies." I need to understand what aspects of BTF are active and available. This is crucial for verifying that the expected metadata is present and functioning as intended. |
| 8/2 | 2026/09/07 20:21 | tool | grepper |
0mArgs:{
"Expression": "config DEBUG_INFO_BTF"
}Results: {
"Output": "lib/Kconfig.debug=378=config DEBUG_INFO_SPLIT\n--\nlib/Kconfig.debug-397-\nlib/Kconfig.debug:398:config DEBUG_INFO_BTF\nlib/Kconfig.debug-399-\tbool \"Generate BTF type information\"\n--\nlib/Kconfig.debug=419=config PAHOLE_HAS_LANG_EXCLUDE\n--\nlib/Kconfig.debug-427-\nlib/Kconfig.debug:428:config DEBUG_INFO_BTF_MODULES\nlib/Kconfig.debug-429-\tbool \"Generate BTF type information for kernel modules\"\n"
} |
| 9/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21337 output: 34 thoughts: 19 Thoughts: **Debugging the Debugging Configuration** Okay, time to dive into the nitty-gritty. I need to take a look at the debugging configuration, so I'm heading straight for `lib/Kconfig.debug`. Specifically, I'm thinking I need to zero in on line 398. That's usually where the meat of a specific debugging setting, or a related grouping, tends to be. I've got a feeling the solution I'm seeking is going to be nearby. Let's see what's what. |
| 10/2 | 2026/09/07 20:21 | tool | read-file |
0mArgs:{
"File": "lib/Kconfig.debug",
"FirstLine": 395,
"LineCount": 35
}Results: {
"Contents": " 395:\t\t to know about the .dwo files and include them.\n 396:\t\t Incompatible with older versions of ccache.\n 397:\t\n 398:\tconfig DEBUG_INFO_BTF\n 399:\t\tbool \"Generate BTF type information\"\n 400:\t\tdepends on !DEBUG_INFO_SPLIT \u0026\u0026 !DEBUG_INFO_REDUCED\n 401:\t\tdepends on !GCC_PLUGIN_RANDSTRUCT || COMPILE_TEST\n 402:\t\tdepends on BPF_SYSCALL\n 403:\t\tdepends on PAHOLE_VERSION \u003e= 122\n 404:\t\t# pahole uses elfutils, which does not have support for Hexagon relocations\n 405:\t\tdepends on !HEXAGON\n 406:\t\thelp\n 407:\t\t Generate deduplicated BTF type information from DWARF debug info.\n 408:\t\t Turning this on requires pahole v1.22 or later, which will convert\n 409:\t\t DWARF type info into equivalent deduplicated BTF type info.\n 410:\t\n 411:\tconfig PAHOLE_HAS_BTF_TAG\n 412:\t\tdef_bool PAHOLE_VERSION \u003e= 123\n 413:\t\tdepends on CC_IS_CLANG\n 414:\t\thelp\n 415:\t\t Decide whether pahole emits btf_tag attributes (btf_type_tag and\n 416:\t\t btf_decl_tag) or not. Currently only clang compiler implements\n 417:\t\t these attributes, so make the config depend on CC_IS_CLANG.\n 418:\t\n 419:\tconfig PAHOLE_HAS_LANG_EXCLUDE\n 420:\t\tdef_bool PAHOLE_VERSION \u003e= 124\n 421:\t\thelp\n 422:\t\t Support for the --lang_exclude flag which makes pahole exclude\n 423:\t\t compilation units from the supplied language. Used in Kbuild to\n 424:\t\t omit Rust CUs which are not supported in version 1.24 of pahole,\n 425:\t\t otherwise it would emit malformed kernel and module binaries when\n 426:\t\t using DEBUG_INFO_BTF_MODULES.\n 427:\t\n 428:\tconfig DEBUG_INFO_BTF_MODULES\n 429:\t\tbool \"Generate BTF type information for kernel modules\"\n"
} |
| 11/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22124 output: 20 thoughts: 34 Thoughts: Okay, here's my take on those thoughts, framed as my own mental process, given my expertise: **Checking BPF and LSM Configurations** Alright, before diving too deep, I need to take a step back and examine the configuration landscape. My initial thought is to immediately check the relevant LSM configs, and potentially any BPF configurations that might be influencing behavior. I have to make sure I don't overlook any details. BPF_LSM is the first thing that comes to mind. Given the focus of this issue, `BPF_LSM` feels like a likely suspect, so I should investigate that first. I'll need to double check how it's set up and if there are any conflicts. |
| 12/2 | 2026/09/07 20:21 | tool | grepper |
0mArgs:{
"Expression": "config BPF_LSM"
}Results: {
"Output": "kernel/bpf/Kconfig=94=source \"kernel/bpf/preload/Kconfig\"\nkernel/bpf/Kconfig-95-\nkernel/bpf/Kconfig:96:config BPF_LSM\nkernel/bpf/Kconfig-97-\tbool \"Enable BPF LSM Instrumentation\"\n"
} |
| 13/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22416 output: 34 thoughts: 19 Thoughts: Here's my summary, as if I were thinking these thoughts: **Examining Kernel BPF Configuration** Okay, let's dive into this. To get a handle on this, I need to start with the configuration options. The `kernel/bpf/Kconfig` file is the obvious place to begin, so let's head straight there. Specifically, I'll zero in around line 96. That's usually where they start the definitions of the core BPF functionality, the heart of the matter, so I bet that's a key spot to get started with this. |
| 14/2 | 2026/09/07 20:21 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Kconfig",
"FirstLine": 90,
"LineCount": 25
}Results: {
"Contents": " 90:\t\t affected hardware.\n 91:\t\n 92:\t\t If you are unsure how to answer this question, answer Y.\n 93:\t\n 94:\tsource \"kernel/bpf/preload/Kconfig\"\n 95:\t\n 96:\tconfig BPF_LSM\n 97:\t\tbool \"Enable BPF LSM Instrumentation\"\n 98:\t\tdepends on BPF_EVENTS\n 99:\t\tdepends on BPF_SYSCALL\n 100:\t\tdepends on SECURITY\n 101:\t\tdepends on BPF_JIT\n 102:\t\thelp\n 103:\t\t Enables instrumentation of the security hooks with BPF programs for\n 104:\t\t implementing dynamic MAC and Audit Policies.\n 105:\t\n 106:\t\t If you are unsure how to answer this question, answer N.\n 107:\t\n 108:\tconfig BPF_JIT_KASAN\n 109:\t\tbool\n 110:\t\tdepends on HAVE_EBPF_JIT_KASAN\n 111:\t\tdepends on KASAN_GENERIC\n 112:\t\tdepends on KASAN_VMALLOC\n 113:\t\tdepends on BPF_JIT\n 114:\t\tdefault y\n"
} |
| 15/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22928 output: 25 thoughts: 418 Thoughts: **Kernel Functionality Review** Okay, let's dissect these kernel modifications. My initial focus is to pinpoint the newly introduced or altered functions. The list points me towards several key areas: * The `bpf_copy_from_user_*` family within `kernel/bpf/helpers.c`. We've got `bpf_copy_from_user_mm`, `bpf_copy_from_user_mm_str`, `bpf_copy_from_user_task`, and `bpf_copy_from_user_task_str`. These are clearly related to BPF programs interacting with user space memory, probably involving different memory spaces and string operations. * In `mm/util.c`, `copy_remote_mm_str` and `copy_remote_vm_str` pop up. Seems like these have something to do with remote memory copies, specifically strings and virtual memory. * `__copy_remote_mm_str` in `mm/memory.c` is also there, which is likely a low-level helper for the above functions. * Then we have some crucial verifier updates in `kernel/bpf/verifier.c`: `check_ptr_to_btf_access`, `type_is_trusted_or_null`, and `check_mem_access`. These are all very important for memory safety within BPF. It seems that they have been added to restrict access of BPF programs to memory. * And finally, `free_bprm` in `fs/exec.c`. That's an interesting one, likely cleaning up the process environment, potentially after an exec call. I also need to check whether `bpf_is_trusted_or_null_btf_ptr` and `bpf_may_fault_on_deref` are static inline functions. They are declared in `include/linux/bpf_verifier.h`, so I'll need to double check the specifics. Now, where is `bpf_may_fault_on_deref` called? I'll grep for it to get the context. |
| 16/2 | 2026/09/07 20:21 | tool | grepper |
0mArgs:{
"Expression": "bpf_may_fault_on_deref"
}Results: {
"Output": "include/linux/bpf_verifier.h=1342=static inline bool bpf_is_trusted_or_null_btf_ptr(enum bpf_reg_type type)\n--\ninclude/linux/bpf_verifier.h-1346-\ninclude/linux/bpf_verifier.h:1347:static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)\ninclude/linux/bpf_verifier.h-1348-{\n--\nkernel/bpf/fixups.c=773=int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)\n--\nkernel/bpf/fixups.c-978-\t\t\tif (bpf_is_ptr_to_mem_or_btf_id(ptr_type) \u0026\u0026\nkernel/bpf/fixups.c:979:\t\t\t bpf_may_fault_on_deref(ptr_type) \u0026\u0026\nkernel/bpf/fixups.c-980-\t\t\t type == BPF_READ) {\n--\nkernel/bpf/fixups.c-989-\t\t\t}\nkernel/bpf/fixups.c:990:\t\t\tif (verifier_bug_if(bpf_may_fault_on_deref(ptr_type), env,\nkernel/bpf/fixups.c-991-\t\t\t\t\t \"access to a fault prone pointer is not rewritten as a probed one\"))\n--\nkernel/bpf/verifier.c=5119=static bool is_load_acq_unsafe(struct bpf_verifier_env *env, int regno,\n--\nkernel/bpf/verifier.c-5131-\t */\nkernel/bpf/verifier.c:5132:\treturn insn-\u003eimm == BPF_LOAD_ACQ \u0026\u0026 bpf_may_fault_on_deref(reg-\u003etype);\nkernel/bpf/verifier.c-5133-}\n--\nkernel/bpf/verifier.c=6072=static int check_ptr_to_btf_access(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-6130-\nkernel/bpf/verifier.c:6131:\tif (atype != BPF_READ \u0026\u0026 bpf_may_fault_on_deref(reg-\u003etype)) {\nkernel/bpf/verifier.c-6132-\t\tverbose(env, \"only read is supported\\n\");\n--\nkernel/bpf/verifier.c-6159-\t\t */\nkernel/bpf/verifier.c:6160:\t\tif (type_is_alloc(reg-\u003etype) \u0026\u0026 !bpf_may_fault_on_deref(reg-\u003etype) \u0026\u0026\nkernel/bpf/verifier.c-6161-\t\t !type_is_non_owning_ref(reg-\u003etype) \u0026\u0026\n--\nkernel/bpf/verifier.c=18287=static bool reg_type_mismatch_ok(enum bpf_reg_type type)\n--\nkernel/bpf/verifier.c-18298-\tcase PTR_TO_MEM:\nkernel/bpf/verifier.c:18299:\t\treturn !bpf_may_fault_on_deref(type);\nkernel/bpf/verifier.c-18300-\tdefault:\n--\nkernel/bpf/verifier.c=18328=static enum bpf_reg_type merge_ptr_types(enum bpf_reg_type type_a,\n--\nkernel/bpf/verifier.c-18333-\nkernel/bpf/verifier.c:18334:\tif (bpf_may_fault_on_deref(type_a) || bpf_may_fault_on_deref(type_b))\nkernel/bpf/verifier.c-18335-\t\ttype_merged |= to_mem ? MEM_RDONLY | PTR_UNTRUSTED :\n"
} |
| 17/2 | 2026/09/07 20:21 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 24808 output: 301 thoughts: 1027 Thoughts: **Analyzing BPF and Memory Access for Fuzzing Potential** Okay, so I'm looking at this patch and my initial thought is, based on `bpf_may_fault_on_deref`'s usage across `kernel/bpf/verifier.c` and `kernel/bpf/fixups.c`—specifically related to `bpf_convert_ctx_accesses`—we need to pick out some focused symbols for targeted fuzzing. I need to be precise here, focusing on the changes, and avoiding the ubiquitous. The list that seems most relevant includes: `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`, `check_ptr_to_btf_access`, `check_mem_access`, `type_is_trusted_or_null`, `bpf_convert_ctx_accesses`, and `free_bprm`. These all seem to be directly touched, or added, and directly related to the new functionality in the patch. Now, let's verify these symbols. I'm checking the naming conventions to ensure the tools are referencing them correctly. For instance, `bpf_copy_from_user_task` is `BPF_CALL_5(bpf_copy_from_user_task, ...)` in `kernel/bpf/helpers.c`, so `bpf_copy_from_user_task` should be good. `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` are defined using `__bpf_kfunc`. `copy_remote_mm_str` and `copy_remote_vm_str` are in mm/util.c, `__copy_remote_mm_str` is in mm/memory.c and finally `free_bprm` is in `fs/exec.c`. I'm also confirming that `check_ptr_to_btf_access` and `type_is_trusted_or_null` are where I expect them, in `kernel/bpf/verifier.c`, and that `check_mem_access` is also in `verifier.c`. I'm also doing a quick scan of `kernel/bpf/verifier.c` for any other modified functions, just to be thorough and ensure I haven't missed anything, confirming `type_is_trusted_or_null`, `check_ptr_to_btf_access`, and `check_mem_access` are the only relevant functions. Next, the Kconfig options, which are going to be key to replicating this. I see `BPF_SYSCALL`, `DEBUG_INFO_BTF`, and `BPF_LSM` are the important ones here. The patch's reasoning is solid: new sleepable kfuncs, refactoring across mm and BPF helpers, the addition of trusted-or-null pointer dereferencing support in the BPF verifier, and the update to `free_bprm`. These modifications touch memory management, BPF verification, and process execution, all of which are accessible via standard syzkaller syscalls. This makes it a great candidate for fuzzing. My conclusion: WorthFuzzing = true; and the configurations and symbols are spot on. I'm ready to call the `set-results` tool now. |
| 18/2 | 2026/09/07 20:21 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF",
"BPF_LSM"
],
"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",
"check_ptr_to_btf_access",
"type_is_trusted_or_null",
"check_mem_access",
"bpf_convert_ctx_accesses",
"free_bprm"
],
"Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm and bpf_copy_from_user_mm_str), refactors remote MM string copying helpers across mm/ and kernel/bpf/, modifies the BPF verifier to allow fault-protected loads through trusted-or-null pointers, and alters mm cleanup in free_bprm(). These changes touch core BPF and memory management execution paths reachable in standard QEMU environments via BPF and exec syscalls.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"DEBUG_INFO_BTF",
"BPF_LSM"
],
"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",
"check_ptr_to_btf_access",
"type_is_trusted_or_null",
"check_mem_access",
"bpf_convert_ctx_accesses",
"free_bprm"
],
"Reasoning": "The patch introduces new sleepable BPF kfuncs (bpf_copy_from_user_mm and bpf_copy_from_user_mm_str), refactors remote MM string copying helpers across mm/ and kernel/bpf/, modifies the BPF verifier to allow fault-protected loads through trusted-or-null pointers, and alters mm cleanup in free_bprm(). These changes touch core BPF and memory management execution paths reachable in standard QEMU environments via BPF and exec syscalls.",
"WorthFuzzing": true
} |
| 19/1 | 2026/09/07 20:21 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 20/2 | 2026/09/07 20:21 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series introduces BPF kfuncs for copying memory from an arbitrary `struct mm_struct` (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`), refactors existing task-based helpers to use these functions, clears `bprm-\u003emm` before dropping the reference in `free_bprm()`, and updates the BPF verifier to allow fault-protected loads through trusted-or-null BTF pointers (`bpf_is_trusted_or_null_btf_ptr`).\n\nKMSAN vs KASAN Applicability:\n- KMSAN detects reads of uninitialized stack/heap/page memory and kernel-to-user memory disclosure (info-leaks).\n- KASAN / LOCKDEP detect memory corruptions, out-of-bounds accesses, use-after-free conditions, and locking errors.\n- The new BPF copy kfuncs properly handle memory initialization: any destination buffers are either fully written or explicitly zeroed via `memset()` upon failure, partial reads, or when `BPF_F_PAD_ZEROS` is requested.\n- No uninitialized struct fields, stack allocations, or kernel-to-user leaks are introduced or exposed.\n- Verifier type tracking and reference counting changes relate to pointer validity and fault handling, areas where KASAN and standard kernel debugging facilities are applicable, not KMSAN.\n\nTherefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit db77af66b3aa07b13dc83aafca1b8b0a0291a1fb
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Sep 7 20:20:53 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/bpf_verifier.h b/include/linux/bpf_verifier.h
index 9727df5af83ab..4f032ad83c675 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1339,6 +1339,11 @@ static inline bool bpf_is_ptr_to_mem_or_btf_id(enum bpf_reg_type type)
}
}
+static inline bool bpf_is_trusted_or_null_btf_ptr(enum bpf_reg_type type)
+{
+ return type == (PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL);
+}
+
static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
{
/*
@@ -1346,7 +1351,9 @@ static inline bool bpf_may_fault_on_deref(enum bpf_reg_type type)
* protection, that is, the ones bpf_convert_ctx_accesses() has to
* turn a BPF_LDX into a BPF_PROBE_MEM one for.
*/
- return type == PTR_TO_BTF_ID || (type_flag(type) & PTR_UNTRUSTED);
+ return type == PTR_TO_BTF_ID ||
+ (type_flag(type) & PTR_UNTRUSTED) ||
+ bpf_is_trusted_or_null_btf_ptr(type);
}
static inline bool bpf_prog_has_arena_ctx_arg(const struct bpf_prog *prog)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23e..d5bde1f71a97e 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3326,6 +3326,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
#ifdef CONFIG_BPF_SYSCALL
+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
#endif
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc8756..d3c564437ad06 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -32,6 +32,10 @@
#include "../../lib/kstrtox.h"
+__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 kernel subsystem is allowing eBPF programs to call this function,
* inside its own verifier_ops->get_func_proto() callback it should return
* bpf_map_lookup_elem_proto, so that verifier can properly check the arguments
@@ -682,22 +686,15 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
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 */
- if (unlikely(flags))
- return -EINVAL;
-
- 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);
+ ret = bpf_copy_from_user_mm(dst, size, user_ptr, mm, flags);
+ if (mm)
+ mmput(mm);
- memset(dst, 0, size);
- /* Return -EFAULT for partial read */
- return ret < 0 ? ret : -EFAULT;
+ return ret;
}
const struct bpf_func_proto bpf_copy_from_user_task_proto = {
@@ -3658,6 +3655,100 @@ __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)
+{
+ int ret;
+
+ if (unlikely(flags))
+ return -EINVAL;
+
+ if (unlikely(!dst__sz))
+ return 0;
+
+ if (unlikely(!mm)) {
+ memset(dst, 0, dst__sz);
+ return -EFAULT;
+ }
+
+ ret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,
+ dst, dst__sz, 0);
+ if (ret == dst__sz)
+ return 0;
+
+ memset(dst, 0, dst__sz);
+ return ret < 0 ? ret : -EFAULT;
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst: Destination address, in kernel space. This buffer must be
+ * at least @dst__sz bytes long
+ * @dst__sz: Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If 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)
+{
+ int ret;
+
+ if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+ return -EINVAL;
+
+ if (unlikely(dst__sz == 0))
+ return 0;
+
+ if (unlikely(!mm)) {
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, dst__sz);
+ else
+ *(char *)dst = '\0';
+ return -EFAULT;
+ }
+
+ ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
+ if (ret < 0) {
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, dst__sz);
+ return ret;
+ }
+
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst + ret, 0, dst__sz - ret);
+
+ return ret + 1;
+}
+
/**
* 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,25 +3772,16 @@ __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))
- return -EINVAL;
-
- if (unlikely(dst__sz == 0))
- return 0;
+ mm = get_task_mm(tsk);
+ ret = bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+ mm, flags);
+ if (mm)
+ mmput(mm);
- ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
- if (ret < 0) {
- if (flags & BPF_F_PAD_ZEROS)
- memset(dst, 0, dst__sz);
- return ret;
- }
-
- if (flags & BPF_F_PAD_ZEROS)
- memset(dst + ret, 0, dst__sz - ret);
-
- return ret + 1;
+ return ret;
}
/* Keep unsigned long in prototype so that kfunc is usable when emitted to
@@ -4924,6 +5006,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..6799d9e080fc5 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,
@@ -6168,6 +6173,15 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
if (ret != PTR_TO_BTF_ID) {
/* just mark; */
+ } else if (bpf_is_trusted_or_null_btf_ptr(reg->type)) {
+ /*
+ * An unchecked load through a trusted-or-NULL pointer is
+ * fault-protected. Any pointer derived from that load must be
+ * untrusted, as a fault produces a NULL value.
+ */
+ clear_trusted_flags(&flag);
+ flag |= PTR_UNTRUSTED;
+
} else if (type_flag(reg->type) & PTR_UNTRUSTED) {
/* If this is an untrusted pointer, all pointers formed by walking it
* also inherit the untrusted flag.
@@ -6644,7 +6658,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
if (!err && t == BPF_READ && value_regno >= 0)
mark_reg_unknown(env, regs, value_regno);
} else if (base_type(reg->type) == PTR_TO_BTF_ID &&
- !type_may_be_null(reg->type)) {
+ (!type_may_be_null(reg->type) ||
+ (t == BPF_READ && bpf_is_trusted_or_null_btf_ptr(reg->type)))) {
err = check_ptr_to_btf_access(env, regs, reg, argno, off, size, t,
value_regno);
} else if (reg->type == CONST_PTR_TO_MAP) {
diff --git a/mm/internal.h b/mm/internal.h
index 38b1165212c94..8264a346d18af 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -25,6 +25,11 @@
struct folio_batch;
struct hstate;
+#ifdef CONFIG_BPF_SYSCALL
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
+#endif
+
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/bpf_iter.c b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
index c69080ca14f5a..99a16a1add70e 100644
--- a/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
@@ -39,10 +39,10 @@ static void test_btf_id_or_null(void)
struct bpf_iter_test_kern3 *skel;
skel = bpf_iter_test_kern3__open_and_load();
- if (!ASSERT_ERR_PTR(skel, "bpf_iter_test_kern3__open_and_load")) {
- bpf_iter_test_kern3__destroy(skel);
+ if (!ASSERT_OK_PTR(skel, "bpf_iter_test_kern3__open_and_load"))
return;
- }
+
+ bpf_iter_test_kern3__destroy(skel);
}
static void do_dummy_read_opts(struct bpf_program *prog, struct bpf_iter_attach_opts *opts)
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/prog_tests/test_struct_ops_maybe_null.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
index 01dc2613c8a51..45af6f00ad908 100644
--- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
@@ -19,15 +19,16 @@ static void maybe_null(void)
struct_ops_maybe_null__destroy(skel);
}
-/* Test that the verifier rejects a program that access a nullable pointer
- * without a check beforehand.
+/*
+ * Test that the verifier accepts a fault-protected read through a nullable
+ * trusted pointer without an explicit NULL check.
*/
-static void maybe_null_fail(void)
+static void maybe_null_no_check(void)
{
struct struct_ops_maybe_null_fail *skel;
skel = struct_ops_maybe_null_fail__open_and_load();
- if (ASSERT_ERR_PTR(skel, "struct_ops_module_fail__open_and_load"))
+ if (!ASSERT_OK_PTR(skel, "struct_ops_maybe_null_fail__open_and_load"))
return;
struct_ops_maybe_null_fail__destroy(skel);
@@ -41,6 +42,6 @@ void test_struct_ops_maybe_null(void)
*/
if (test__start_subtest("maybe_null"))
maybe_null();
- if (test__start_subtest("maybe_null_fail"))
- maybe_null_fail();
+ if (test__start_subtest("maybe_null_no_check"))
+ maybe_null_no_check();
}
diff --git a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
index accc42e01f8a8..825fe7a92d74f 100644
--- a/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
+++ b/tools/testing/selftests/bpf/prog_tests/tp_btf_nullable.c
@@ -2,6 +2,31 @@
#include <test_progs.h>
#include "test_tp_btf_nullable.skel.h"
+#include "test_tp_btf_nullable_runtime.skel.h"
+
+static void test_nullable_runtime(void)
+{
+ struct test_tp_btf_nullable_runtime *skel;
+
+ skel = test_tp_btf_nullable_runtime__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ skel->bss->monitored_tid = sys_gettid();
+
+ if (!ASSERT_OK(test_tp_btf_nullable_runtime__attach(skel), "attach"))
+ goto out;
+
+ if (!ASSERT_OK(trigger_module_test_read(2), "trigger"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->calls, 2, "calls");
+ ASSERT_EQ(skel->bss->nonnull_len, 2, "nonnull_len");
+ ASSERT_EQ(skel->bss->null_len, 0, "null_len");
+
+out:
+ test_tp_btf_nullable_runtime__destroy(skel);
+}
void test_tp_btf_nullable(void)
{
@@ -11,4 +36,7 @@ void test_tp_btf_nullable(void)
}
RUN_TESTS(test_tp_btf_nullable);
+
+ if (test__start_subtest("runtime"))
+ test_nullable_runtime();
}
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/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
index 725d73c9ffe13..163124793d0a6 100644
--- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -2,22 +2,34 @@
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
-/* Ensure module parameter has PTR_MAYBE_NULL */
+extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
+extern void bpf_task_release(struct task_struct *p) __ksym;
+
+/*
+ * Ensure the module tracepoint argument is trusted-or-NULL while allowing
+ * a fault-protected read without an explicit NULL check.
+ */
SEC("tp_btf/bpf_testmod_test_raw_tp_null_tp")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_sk_buff")
int test_raw_tp_null_bpf_testmod_test_raw_tp_null_arg_1(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
}
-/* Check NULL marking */
+/*
+ * Ensure sched_pi_setprio's second argument is trusted-or-NULL while allowing
+ * a fault-protected read without an explicit NULL check.
+ */
SEC("tp_btf/sched_pi_setprio")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_task_struct")
int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) {
asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);
return 0;
@@ -60,7 +72,8 @@ int test_tp_btf_signal_deliver_info_no_deref(void *ctx)
}
SEC("tp_btf/sched_process_wait")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success __log_level(2)
+__msg("R1=trusted_ptr_or_null_pid")
int test_raw_tp_null_sched_process_wait_arg_1(void *ctx)
{
asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
@@ -75,3 +88,58 @@ int test_raw_tp_null_sched_process_wait_arg_1_checked(void *ctx)
"r1 = *(u32 *)(r1 +0);" ::: __clobber_all);
return 0;
}
+
+SEC("tp_btf/sched_pi_setprio")
+__failure __log_level(2)
+__msg("R1=untrusted_ptr_task_struct")
+__msg("R1 must be a rcu pointer")
+int BPF_PROG(trusted_or_null_walk_is_untrusted, struct task_struct *task,
+ struct task_struct *pi_task)
+{
+ struct task_struct *parent, *acquired;
+
+ parent = pi_task->real_parent;
+ acquired = bpf_task_acquire(parent);
+ if (acquired)
+ bpf_task_release(acquired);
+ return 0;
+}
+
+SEC("tp_btf/sched_pi_setprio")
+__failure __msg("R1 must be a rcu pointer")
+int BPF_PROG(derived_ptr_null_check_does_not_restore_trust,
+ struct task_struct *task, struct task_struct *pi_task)
+{
+ struct task_struct *parent, *acquired;
+
+ parent = pi_task->real_parent;
+ if (!parent)
+ return 0;
+
+ acquired = bpf_task_acquire(parent);
+ if (acquired)
+ bpf_task_release(acquired);
+
+ return 0;
+}
+
+/*
+ * In contrast, checking the original trusted-or-NULL pointer removes
+ * PTR_MAYBE_NULL while retaining PTR_TRUSTED.
+ */
+SEC("tp_btf/sched_pi_setprio")
+__success
+int BPF_PROG(original_ptr_null_check_retains_trust,
+ struct task_struct *task, struct task_struct *pi_task)
+{
+ struct task_struct *acquired;
+
+ if (!pi_task)
+ return 0;
+
+ acquired = bpf_task_acquire(pi_task);
+ if (acquired)
+ bpf_task_release(acquired);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
index cf0547a613ffc..b7914224ba194 100644
--- a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
@@ -7,7 +7,7 @@
#include "bpf_misc.h"
SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx)
{
return nullable_ctx->len;
@@ -21,4 +21,47 @@ int BPF_PROG(handle_tp_btf_nullable_bare2, struct bpf_testmod_test_read_ctx *nul
return 0;
}
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__success
+int BPF_PROG(handle_tp_btf_nullable_mem, struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ return nullable_ctx->buf[0];
+}
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure __msg("pointer arithmetic on trusted_ptr_or_null_ prohibited")
+int BPF_PROG(handle_tp_btf_nullable_arith, struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ asm volatile("%[ctx] += 1" : [ctx] "+r"(nullable_ctx));
+ return nullable_ctx->len;
+}
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(handle_tp_btf_nullable_atomic_rmw,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ asm volatile ("r1 = %[ctx];"
+ "w2 = 1;"
+ "lock *(u32 *)(r1 + %[len]) += w2;"
+ :
+ : [ctx] "r"(nullable_ctx),
+ __imm_const(len,
+ offsetof(struct bpf_testmod_test_read_ctx,
+ len))
+ : "r1", "r2", "memory");
+ return 0;
+}
+
+#ifdef __BPF_FEATURE_LOAD_ACQ_STORE_REL
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+__failure
+__msg("BPF_ATOMIC loads from R{{[0-9]+}} trusted_ptr_or_null_")
+int BPF_PROG(handle_tp_btf_nullable_load_acquire,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ return __atomic_load_n(&nullable_ctx->len, __ATOMIC_ACQUIRE);
+}
+#endif
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c
new file mode 100644
index 0000000000000..5c9c7f94040dc
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+int monitored_tid;
+int calls;
+__u64 nonnull_len;
+__u64 null_len;
+
+SEC("tp_btf/bpf_testmod_test_nullable_bare_tp")
+int BPF_PROG(handle_nullable_runtime,
+ struct bpf_testmod_test_read_ctx *nullable_ctx)
+{
+ __u32 tid = bpf_get_current_pid_tgid();
+ __u64 len;
+ int call;
+
+ if (tid != monitored_tid)
+ return 0;
+
+ len = nullable_ctx->len;
+ call = calls++;
+
+ if (call == 0)
+ nonnull_len = len;
+ else if (call == 1)
+ null_len = len;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index c724bf389f5c6..fac133d90f5e6 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -162,13 +162,13 @@ __naked int disabled_hook_test3(void *ctx)
SEC("lsm/mmap_file")
__description("not null checking nullable pointer in bpf_lsm_mmap_file")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
int BPF_PROG(no_null_check, struct file *file)
{
- struct inode *inode;
+ ino_t ino;
- inode = file->f_inode;
- __sink(inode);
+ ino = file->f_inode->i_ino;
+ __sink(ino);
return 0;
}
@@ -188,6 +188,16 @@ int BPF_PROG(null_check, struct file *file)
return 0;
}
+SEC("lsm/mmap_file")
+__description("store through trusted-or-null file is rejected")
+__failure
+__msg("R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(store_through_trusted_or_null_file, struct file *file)
+{
+ file->f_flags = 0;
+ return 0;
+}
+
SEC("lsm_cgroup/file_open")
__description("sleepable lsm_cgroup program is rejected")
__failure __msg("Program of this type cannot be sleepable")
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
index 55398c04290a8..17c1542cc7e17 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
@@ -100,4 +100,18 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
return 0;
}
+SEC("lsm.s/inode_rename")
+__success __log_level(2)
+__msg("R{{[0-9]+}}=trusted_ptr_or_null_inode")
+int BPF_PROG(inode_rename_no_null_check, struct inode *old_dir,
+ struct dentry *old_dentry, struct inode *new_dir,
+ struct dentry *new_dentry, unsigned int flags)
+{
+ ino_t ino = new_dentry->d_inode->i_ino;
+
+ if (ino == 0)
+ return -EACCES;
+ return 0;
+}
+
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
index 8f0c45421f893..2a0813258183e 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_reject.c
@@ -159,18 +159,4 @@ int BPF_PROG(path_d_path_kfunc_non_lsm, struct path *path, struct file *f)
return 0;
}
-SEC("lsm.s/inode_rename")
-__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
-int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
- struct inode *new_dir, struct dentry *new_dentry,
- unsigned int flags)
-{
- struct inode *inode = new_dentry->d_inode;
- ino_t ino;
-
- ino = inode->i_ino;
- if (ino == 0)
- return -EACCES;
- return 0;
-}
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index f798bbbb4d13c..2ba6a83d243f5 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -702,6 +702,7 @@ bpf_testmod_test_read(struct file *file, struct kobject *kobj,
if (bpf_testmod_loop_test(101) > 100)
trace_bpf_testmod_test_read(current, &ctx);
+ trace_bpf_testmod_test_nullable_bare_tp(&ctx);
trace_bpf_testmod_test_nullable_bare_tp(NULL);
/* Magic number to enable writable tp */
diff --git a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
index ec724d7b33d18..ecaa36355caec 100644
--- a/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
+++ b/tools/testing/selftests/sched_ext/maybe_null_fail_dsp.bpf.c
@@ -7,14 +7,13 @@
char _license[] SEC("license") = "GPL";
-u64 vtime_test;
-
void BPF_STRUCT_OPS(maybe_null_running, struct task_struct *p)
{}
void BPF_STRUCT_OPS(maybe_null_fail_dispatch, s32 cpu, struct task_struct *p)
{
- vtime_test = p->scx.dsq_vtime;
+ /* Pointer arithmetic on a trusted-or-NULL pointer must be rejected. */
+ asm volatile("%[p] += 0" : [p] "+r"(p));
}
SEC(".struct_ops.link")
|
| 21/3 | 2026/09/07 20:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 17206 output: 30 thoughts: 1282 Thoughts: **Analyzing the Patch Series for KMSAN Needs** Okay, let's break this down. My goal is to determine if this patch series necessitates a dedicated KMSAN fuzzing session, beyond the standard KASAN coverage we already have. We need to be on the lookout for code changes that *introduce* or *expose* uninitialized memory reads, the kind of issues KMSAN is specifically designed to catch. First, I'm focusing on the changes. 1. `fs/exec.c`: This change, setting `bprm->mm = NULL`, is a memory management lifecycle change. It's about preventing UAF and dangling pointer problems. This seems like a reasonable precaution; however, it's about making code safe not about introducing new uninitialized memory reads. I don't see anything here that screams for KMSAN attention. 2. `include/linux/bpf_verifier.h`, `kernel/bpf/verifier.c`: This is the interesting section. We're getting a bunch of updates related to BPF and `BTF`. We're enabling safe dereferencing and allowing BPF to safely access `bprm->mm`. The addition of `bpf_is_trusted_or_null_btf_ptr` and allowing fault-protected dereferences sounds like it could potentially introduce subtle issues. The `PTR_UNTRUSTED` marking is interesting. While this section deals with potentially accessing memory, it's primarily about *controlling* access. The goal appears to be *prevention* of bad things happening, not the *introduction* of uninitialized read risks. The changes are designed to protect against faulting and dereferencing null pointers. We're making things *safer*, not necessarily riskier from a KMSAN perspective. We need to double-check this because the `PTR_UNTRUSTED` might have uninitialized read. 3. `kernel/bpf/helpers.c`: Now we're getting to the heart of memory copying: `bpf_copy_from_user_mm` and its string variant, `bpf_copy_from_user_mm_str`. It refactors `bpf_copy_from_user_task` to use the new helper, and registers kfuncs. This is a potential KMSAN target. We have `dst` memory that we're copying *into*. The logic includes checks for a null `mm`, and sets dst to zero. So there is a possibility that dst is uninitialized. The checks for an `!mm` look promising, but need confirmation. If we don't have a valid `mm` it sets dst to zero. This looks like a reasonable precaution, however, if `mm` *is* valid, it's worth checking `access_remote_vm`, which is an issue. With the `copy_remote_mm_str` function, there are cases where the destination *might* be partially initialized. It seems like it's taking extra steps to ensure this is handled. With `BPF_F_PAD_ZEROS` we can be setting the memory to zero. Need to examine `access_remote_vm` and `copy_remote_mm_str` to verify these assumptions. 4. `mm/memory.c`, `mm/nommu.c`, `mm/util.c`, `mm/internal.h`, `include/linux/mm.h`: This is mostly refactoring the remote memory copy functions, renaming and adding wrappers. The key function to look at here is `__copy_remote_mm_str` and `copy_remote_mm_str`. This is where the actual memory copying happens. We need to examine this code to see if there is any window in which the destination buffer isn't properly initialized. If there's a partial read or NUL-termination issue we need to be very concerned. 5. Selftests: Great, they added tests! However, testing is not enough. In summary, I see the greatest potential for KMSAN exposure in the `bpf_copy_from_user_mm` functions and their string variant. I need to dive into the implementation of `__copy_remote_mm_str`, `copy_remote_mm_str`, and the calls within the new helper functions to make a definitive determination. I also need to verify that all memory is initialized appropriately in `BPF_F_PAD_ZEROS`. While the intention seems to be safe, I need to ensure that the code is *provably* safe from uninitialized read risks. The BPF changes require thorough inspection regarding the use of `PTR_UNTRUSTED`. |
| 22/3 | 2026/09/07 20:21 | tool | grepper |
0mArgs:{
"Expression": "__copy_remote_mm_str",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/internal.h=26=struct hstate;\n--\nmm/internal.h-28-#ifdef CONFIG_BPF_SYSCALL\nmm/internal.h:29:int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,\nmm/internal.h-30-\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/07 20:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 20006 output: 34 thoughts: 32 Thoughts: Here's my take on the thought process, as requested: **Diving into `__copy_remote_mm_str`** Okay, so I need to understand what `__copy_remote_mm_str` is doing. Given my deep knowledge of memory management within the kernel, I immediately think, "Let's crack open `mm/memory.c`." It's likely in that file, and a good bet is around line 7334. That's a reasonable starting point, given my experience with these kinds of functions within the kernel source. I figure I'll start there and see what jumps out. I'm not expecting a quick answer - these things always take a bit of digging to fully grasp the nuances. Let's see what the code actually *says*. |
| 24/3 | 2026/09/07 20:21 | tool | read-file |
0mArgs:{
"File": "mm/memory.c",
"FirstLine": 7330,
"LineCount": 85
}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}\n7410:\t#endif /* CONFIG_BPF_SYSCALL */\n7411:\t\n7412:\t/*\n7413:\t * Print the name of a VMA.\n7414:\t */\n"
} |
| 25/3 | 2026/09/07 20:21 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21412 output: 335 thoughts: 2302 Thoughts: **Analysis of the Proposed Kernel Patch** Okay, let's break this down meticulously. My initial assessment is that this patch primarily focuses on refactoring and tightening up some BPF functionality with a focus on safety and correctness, rather than introducing any novel memory initialization issues. Let's delve into the details. The first point to consider is the refactoring of the BPF helpers used for copying data from user space. Specifically, `bpf_copy_from_user_task` and `bpf_copy_from_user_task_str` are being revamped to use `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` respectively, which operate directly on `struct mm_struct *`. This is a clear improvement in terms of code organization and reusability, allowing functions like `copy_remote_mm_str` (which handles the actual memory transfer) to be used with different memory spaces. Importantly, the design of these kfuncs, particularly regarding `dst`, is well-understood: memory for `dst` is either zeroed on failure or initialized using helpers like `access_remote_vm` or `copy_remote_mm_str`. The existing logic for zeroing out the destination buffer on failure/partial copy remains intact, which is critical. The concern here is not introducing a case of reading uninitialized data. The `free_bprm` change, where `bprm->mm` is explicitly set to `NULL` *before* `mmput(mm)` is called, is a straightforward and correct approach to avoid potential use-after-free (UAF) scenarios. This explicitly nullifies the pointer after the memory management operation. KASAN would be the tool to detect any issues related to this pointer. The verifier changes are also crucial. The patch is allowing the BPF programs to read trusted-or-null pointers, protected by fault-safe operations. This mechanism transforms pointer loads into probe reads, and marks the retrieved pointer as untrusted, which is essential for ensuring that BPF programs cannot directly dereference arbitrary user-provided addresses. This also extends to treating `bprm->mm` as trusted-or-null. This is all about enhancing the safety of the BPF environment. It’s about verifying what can be accessed via BPF. Now, concerning KMSAN, my assessment is that this patch should *not* introduce KMSAN-relevant issues. The patch doesn't introduce any new structures that could be partially initialized or new instances where kernel data could be leaked to user space via copies. The memory handling within the BPF functions is already robust and well-defined, with zeroing on failure or padding flags. The pointer manipulation focuses on validity and fault tolerance, not introducing memory initialization flaws. KASAN and the verifier will handle any potential UAF, or out-of-bounds access. The entire logic is designed to prevent uninitialized memory reads. Therefore, considering that this patch does not introduce any cases where uninitialized memory *could* be read, where data might leak to user space, or where new data structures are allocated without proper initialization, I conclude that `NeedsKMSAN` is `false`. The changes are safety-focused refactoring, and are well within the existing memory protection framework, and any problems would be caught by KASAN/verifier checks. |
| 26/3 | 2026/09/07 20:21 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces BPF kfuncs for copying memory from an arbitrary `struct mm_struct` (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`), refactors existing task-based helpers to use these functions, clears `bprm-\u003emm` before dropping the reference in `free_bprm()`, and updates the BPF verifier to allow fault-protected loads through trusted-or-null BTF pointers (`bpf_is_trusted_or_null_btf_ptr`).\n\nKMSAN vs KASAN Applicability:\n- KMSAN detects reads of uninitialized stack/heap/page memory and kernel-to-user memory disclosure (info-leaks).\n- KASAN / LOCKDEP detect memory corruptions, out-of-bounds accesses, use-after-free conditions, and locking errors.\n- The new BPF copy kfuncs properly handle memory initialization: any destination buffers are either fully written or explicitly zeroed via `memset()` upon failure, partial reads, or when `BPF_F_PAD_ZEROS` is requested.\n- No uninitialized struct fields, stack allocations, or kernel-to-user leaks are introduced or exposed.\n- Verifier type tracking and reference counting changes relate to pointer validity and fault handling, areas where KASAN and standard kernel debugging facilities are applicable, not KMSAN.\n\nTherefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces BPF kfuncs for copying memory from an arbitrary `struct mm_struct` (`bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str`), refactors existing task-based helpers to use these functions, clears `bprm-\u003emm` before dropping the reference in `free_bprm()`, and updates the BPF verifier to allow fault-protected loads through trusted-or-null BTF pointers (`bpf_is_trusted_or_null_btf_ptr`).\n\nKMSAN vs KASAN Applicability:\n- KMSAN detects reads of uninitialized stack/heap/page memory and kernel-to-user memory disclosure (info-leaks).\n- KASAN / LOCKDEP detect memory corruptions, out-of-bounds accesses, use-after-free conditions, and locking errors.\n- The new BPF copy kfuncs properly handle memory initialization: any destination buffers are either fully written or explicitly zeroed via `memset()` upon failure, partial reads, or when `BPF_F_PAD_ZEROS` is requested.\n- No uninitialized struct fields, stack allocations, or kernel-to-user leaks are introduced or exposed.\n- Verifier type tracking and reference counting changes relate to pointer validity and fault handling, areas where KASAN and standard kernel debugging facilities are applicable, not KMSAN.\n\nTherefore, dedicated KMSAN fuzzing is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|