[PATCH bpf-next 2/5] bpf: Add user memory access kfuncs for mm_struct
From: Anastasios Papagiannis
Date: Tue Aug 11 2026 - 07:23:04 EST
When security_bprm_check runs, the arg and env strings for the exec have
been copied into bprm->mm. The new address space has not been associated
yet with a task_struct until exec_mmap(), so existing BPF user memory
helpers can only read from the calling task's old address space.
Add bpf_copy_from_user_mm() and bpf_copy_from_user_mm_str() kfuncs. Both
take a struct mm_struct pointer directly, allowing callers to access
trusted address spaces that are not associated with a task_struct.
bpf_copy_from_user_mm() has similar semantics to
bpf_copy_from_user_task(). bpf_copy_from_user_mm_str() copies one
NUL-terminated string and returns its size including the NUL terminator.
It accepts BPF_F_PAD_ZEROS to clear unused destination bytes on success.
Register both kfuncs with the common BPF_EVENTS kfunc set and mark them
KF_SLEEPABLE because accessing the remote address space can fault. The next
patch marks linux_binprm->mm as trusted-or-null, allowing BPF LSM programs
attached to security_bprm_check to read arguments beginning at bprm->p
after a NULL check and reject an exec based on its command-line arguments.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@xxxxxxxxx>
---
kernel/bpf/helpers.c | 2 +
kernel/trace/bpf_trace.c | 83 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 6388b6b23e49..9ba3362832cd 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4931,6 +4931,8 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_local_irq_save)
BTF_ID_FLAGS(func, bpf_local_irq_restore)
#ifdef CONFIG_BPF_EVENTS
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 891897f8a1b3..8931b3b2fc9d 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -24,6 +24,7 @@
#include <linux/key.h>
#include <linux/namei.h>
#include <linux/file.h>
+#include <linux/mm.h>
#include <net/bpf_sk_storage.h>
@@ -3619,6 +3620,88 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid
return bpf_send_signal_common(sig, type, task, value);
}
+/**
+ * bpf_copy_from_user_mm() - Copy data from an address space
+ * @dst: Destination address, in kernel space
+ * @dst__sz: Number of bytes to copy
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: Reserved for future use; must be zero
+ *
+ * Copies data from the user address space associated with @mm. The destination
+ * is zeroed if the requested number of bytes cannot be copied in full.
+ *
+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy
+ * fails or is partial.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+ const void __user *unsafe_ptr__ign,
+ struct mm_struct *mm, u64 flags)
+{
+ int ret;
+
+ if (unlikely(flags))
+ return -EINVAL;
+
+ if (unlikely(!dst__sz))
+ return 0;
+
+ ret = access_remote_vm(mm, (unsigned long)unsafe_ptr__ign,
+ dst, dst__sz, 0);
+ if (ret == dst__sz)
+ return 0;
+
+ memset(dst, 0, dst__sz);
+ return ret < 0 ? ret : -EFAULT;
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst: Destination address, in kernel space. This buffer must be
+ * at least @dst__sz bytes long
+ * @dst__sz: Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If BPF_F_PAD_ZEROS is set, the unused portion of @dst is cleared on success
+ * and all of @dst is cleared on failure.
+ *
+ * Return: The number of copied bytes including the NUL terminator on success,
+ * or a negative error code on failure.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+ const void __user *unsafe_ptr__ign,
+ struct mm_struct *mm, u64 flags)
+{
+ int ret;
+
+ if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+ return -EINVAL;
+
+ if (unlikely(!dst__sz))
+ return 0;
+
+ ret = copy_remote_mm_str(mm, (unsigned long)unsafe_ptr__ign,
+ dst, dst__sz, 0);
+ if (ret < 0)
+ goto err;
+
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst + ret, 0, dst__sz - ret);
+
+ return ret + 1;
+
+err:
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, dst__sz);
+ return ret;
+}
+
__bpf_kfunc int bpf_probe_read_user_dynptr(const struct bpf_dynptr *dptr, u64 off,
u64 size, const void __user *unsafe_ptr__ign)
{
--
2.55.0