* [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct
@ 2026-09-08 13:52 Anastasios Papagiannis
2026-09-08 13:52 ` [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str() Anastasios Papagiannis
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:52 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
On MMU systems, during exec, argument and environment strings are copied
into the new address space held by struct linux_binprm before that address
space is installed on the task_struct. Existing BPF user memory helpers
operate on the current address space or one associated with a task_struct.
Because no task_struct refers to the new address space at this point,
programs cannot access these strings from the bprm_check_security LSM
hook.
This series adds two sleepable BPF kfuncs for copying bytes or
NUL-terminated strings from a trusted struct mm_struct. It also marks
linux_binprm->mm as trusted-or-null, allowing BPF LSM programs to pass it
to the kfuncs after a NULL check and inspect exec arguments before
allowing the exec to continue.
To make the trusted-or-null annotation safe, the exec paths are hardened
to clear bprm->mm before dropping the reference it owns.
On NOMMU systems, exec argument and environment strings remain in
bprm->page[] until they are transferred to the new process stack. They
cannot be accessed through bprm->mm at the bprm_check_security hook.
The new kfuncs remain available on NOMMU for address ranges represented
by a supplied struct mm_struct.
The series adds selftests covering both kfuncs while reading argument and
environment strings from linux_binprm during exec.
Changes in v6:
- Drop redundant extern keywords and CONFIG_BPF_SYSCALL guards from the
remote memory copy declarations.
- Drop the explicit bpf_copy_from_user_mm() prototype and share copy
logic through inlineable static internal helpers instead of calling
between kfunc/helper entry points.
- Validate flags and zero-length requests before acquiring the task's
mm, preserving existing behavior and ensuring internal helpers are
called only with a live mm.
- Drop the verifier relaxation for unchecked reads through trusted-or-null
pointers and its tests.
- Fix the existing LSM selftest to explicitly NULL-check bprm->mm after
marking the field trusted-or-null.
Changes in v5:
- Move the shared wrappers to mm/util.c and handle zero-length requests
at the entry points.
Changes in v4:
- Add negative verifier tests for atomic RMW and load-acquire accesses
through trusted-or-null BTF pointers.
- Preserve explicit nullability-marking coverage for tracepoint
arguments, dentry->d_inode, and sched_ext .dispatch.
- Use the already-nullable mmap_file argument for the negative store
test, avoiding dependency on the later linux_binprm->mm marking.
- Clarify the bprm->mm lifetime invariant and move its lifetime fix
before the mm_struct kfunc patch.
- Reword the trusted-or-null read change in imperative mood and remove
its redundant before-and-after summary.
- Document that the existing task-based user-memory interfaces delegate
to the new mm-based implementations, reorder the string-copy kfuncs to
remove an unnecessary declaration, and annotate the remaining
declaration with __bpf_kfunc.
Changes in v3:
- Replace the linux_binprm-specific kfuncs with generic struct mm_struct
kfuncs, as suggested by Andrii Nakryiko.
- Move the kfuncs next to the existing user memory helpers and make the
task-based variants delegate to the new mm-based implementations, as
suggested by Andrii Nakryiko.
- Clear bprm->mm before dropping its reference on exec error paths.
- Mark linux_binprm->mm as trusted-or-null.
- Allow fault-protected reads through trusted-or-null BTF pointers to
preserve compatibility with existing BPF programs, as suggested by
Andrii Nakryiko.
- Add verifier and runtime selftests for trusted-or-null BTF pointer
reads.
- Rename __copy_remote_vm_str() to __copy_remote_mm_str(), as suggested
by Andrii Nakryiko.
- Clarify that reading exec strings through bprm->mm is limited to MMU
systems, while the generic mm-based kfuncs remain available on NOMMU.
Changes in v2:
- Register the kfuncs on NOMMU systems and return -EOPNOTSUPP when called,
as suggested by Justin Suess.
- Add selftest coverage for reading environment strings, as suggested by
Justin Suess.
- Clarify that copy_remote_mm_str() leaves the destination untouched when
called with a zero-length buffer.
- Use sizeof() instead of hardcoded argument lengths in the selftests.
- Use ~0ULL for invalid-flags checks in the selftests.
v5:
https://lore.kernel.org/bpf/20260907165220.52431-1-tasos.papagiannnis@gmail.com/
v4:
https://lore.kernel.org/bpf/20260904145340.40212-1-tasos.papagiannnis@gmail.com/
v3:
https://lore.kernel.org/bpf/20260831092305.42062-1-tasos.papagiannnis@gmail.com/
v2:
https://lore.kernel.org/bpf/20260820131801.68759-1-tasos.papagiannnis@gmail.com/
v1:
https://lore.kernel.org/bpf/20260812111140.7762-1-tasos.papagiannnis@gmail.com/
Anastasios Papagiannis (5):
mm: Add copy_remote_mm_str()
exec: Clear bprm->mm before dropping its reference
bpf: Add user memory access kfuncs for mm_struct
bpf: Mark linux_binprm->mm as trusted-or-null
selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm
fs/exec.c | 7 +-
include/linux/mm.h | 8 +-
kernel/bpf/helpers.c | 130 ++++++++++++++++--
kernel/bpf/verifier.c | 5 +
mm/internal.h | 3 +
mm/memory.c | 41 +-----
mm/nommu.c | 41 +-----
mm/util.c | 62 +++++++++
.../bpf/prog_tests/copy_from_user_bprm.c | 72 ++++++++++
.../selftests/bpf/progs/copy_from_user_bprm.c | 123 +++++++++++++++++
tools/testing/selftests/bpf/progs/lsm.c | 5 +-
11 files changed, 400 insertions(+), 97 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
create mode 100644 tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
base-commit: 1b7415bf70be95b9a1e7e87d544867881065613f
--
2.55.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str()
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-09-08 13:52 ` Anastasios Papagiannis
2026-09-08 14:07 ` David Hildenbrand (Arm)
2026-09-08 13:52 ` [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
` (3 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:52 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
copy_remote_vm_str() gets the target address space from a struct
task_struct. This does not work for an address space that exists but is
not yet associated with a task_struct, such as the mm held by struct
linux_binprm during exec.
Add copy_remote_mm_str(), which operates directly on a struct mm_struct.
Use a common internal interface for the MMU and NOMMU implementations
and define both public wrappers in mm/util.c. Preserve the existing
copy_remote_vm_str() behavior, including handling zero-length requests
before acquiring the task's mm.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
include/linux/mm.h | 8 +++---
mm/internal.h | 3 +++
mm/memory.c | 41 ++----------------------------
mm/nommu.c | 41 ++----------------------------
mm/util.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 73 insertions(+), 82 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23..6f10ce315eaa 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3325,10 +3325,10 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags);
-#ifdef CONFIG_BPF_SYSCALL
-extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
- void *buf, int len, unsigned int gup_flags);
-#endif
+int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
+int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
long get_user_pages_remote(struct mm_struct *mm,
unsigned long start, unsigned long nr_pages,
diff --git a/mm/internal.h b/mm/internal.h
index 38b1165212c9..557b29381355 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -25,6 +25,9 @@
struct folio_batch;
struct hstate;
+int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+ void *buf, int len, unsigned int gup_flags);
+
struct huge_bootmem_page {
struct list_head list;
struct hstate *hstate;
diff --git a/mm/memory.c b/mm/memory.c
index 8b0c2c735d3d..fe2f5e988fb9 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 498e01ee40b0..98596e60311f 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 bf0513d1d3d0..2eca27b02791 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;
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-09-08 13:52 ` [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str() Anastasios Papagiannis
@ 2026-09-08 13:52 ` Anastasios Papagiannis
2026-09-08 14:06 ` sashiko-bot
2026-09-08 13:53 ` [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
` (2 subsequent siblings)
4 siblings, 1 reply; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:52 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
Once mmput() drops the final reference to bprm->mm, the pointer must no
longer remain accessible through struct linux_binprm.
The successful exec path and the bprm initialization error path already
clear bprm->mm when ownership is transferred or released. Do the same in
free_bprm() before calling mmput().
This is required for BPF kfuncs where bprm->mm is either NULL or points
to a live mm_struct to ensure safe access.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
Reviewed-by: Sun Jian <sun.jian.kdev@gmail.com>
---
fs/exec.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index 745f6eb5279e..4ddd403fd91c 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);
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-09-08 13:52 ` [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str() Anastasios Papagiannis
2026-09-08 13:52 ` [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
@ 2026-09-08 13:53 ` Anastasios Papagiannis
2026-09-08 16:18 ` bot+bpf-ci
2026-09-08 13:53 ` [PATCH bpf-next v6 4/5] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
2026-09-08 13:53 ` [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
4 siblings, 1 reply; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:53 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
On CONFIG_MMU kernels, when security_bprm_check() runs, the argument and
environment strings for the exec have been copied into bprm->mm. The new
address space is not associated with a task_struct until exec_mmap(), so
existing BPF user memory helpers cannot access it.
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.
Refactor the task-based helpers and the new mm-based kfuncs to share
static internal implementations. The task-based interfaces validate their
arguments before acquiring and holding a reference to the task's mm for
the copy. No behavior change is intended for the existing task-based
interfaces.
Register both new kfuncs and mark them KF_SLEEPABLE because accessing a
remote address space can fault.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
kernel/bpf/helpers.c | 130 +++++++++++++++++++++++++++++++++++++++----
1 file changed, 118 insertions(+), 12 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc875..3338bebdd86e 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -679,9 +679,44 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
.arg3_type = ARG_ANYTHING,
};
+static int __bpf_copy_from_user_mm(void *dst, u32 size,
+ const void __user *user_ptr,
+ struct mm_struct *mm)
+{
+ int ret;
+
+ ret = access_remote_vm(mm, (unsigned long)user_ptr, dst, size, 0);
+ if (ret == size)
+ return 0;
+
+ memset(dst, 0, size);
+ /* Return -EFAULT for partial read */
+ return ret < 0 ? ret : -EFAULT;
+}
+
+static int __bpf_copy_from_user_mm_str(void *dst, u32 size,
+ const void __user *user_ptr,
+ struct mm_struct *mm, u64 flags)
+{
+ int ret;
+
+ ret = copy_remote_mm_str(mm, (unsigned long)user_ptr, dst, size, 0);
+ if (ret < 0) {
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst, 0, size);
+ return ret;
+ }
+
+ if (flags & BPF_F_PAD_ZEROS)
+ memset(dst + ret, 0, size - ret);
+
+ return ret + 1;
+}
+
BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
const void __user *, user_ptr, struct task_struct *, tsk, u64, flags)
{
+ struct mm_struct *mm;
int ret;
/* flags is not used yet */
@@ -691,13 +726,16 @@ BPF_CALL_5(bpf_copy_from_user_task, void *, dst, u32, size,
if (unlikely(!size))
return 0;
- ret = access_process_vm(tsk, (unsigned long)user_ptr, dst, size, 0);
- if (ret == size)
- return 0;
+ mm = get_task_mm(tsk);
+ if (!mm) {
+ memset(dst, 0, size);
+ return -EFAULT;
+ }
- memset(dst, 0, size);
- /* Return -EFAULT for partial read */
- return ret < 0 ? ret : -EFAULT;
+ ret = __bpf_copy_from_user_mm(dst, size, user_ptr, mm);
+ mmput(mm);
+
+ return ret;
}
const struct bpf_func_proto bpf_copy_from_user_task_proto = {
@@ -3658,6 +3696,68 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user
return ret + 1;
}
+/**
+ * bpf_copy_from_user_mm() - Copy data from an address space
+ * @dst: Destination address, in kernel space
+ * @dst__sz: Number of bytes to copy
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: Reserved for future use; must be zero
+ *
+ * Copies data from the user address space associated with @mm. The destination
+ * is zeroed if an attempted copy cannot be completed in full. Unsupported
+ * flags return -EINVAL without modifying @dst.
+ *
+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy
+ * fails or is partial.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+ const void __user *unsafe_ptr__ign,
+ struct mm_struct *mm, u64 flags)
+{
+ if (unlikely(flags))
+ return -EINVAL;
+
+ if (unlikely(!dst__sz))
+ return 0;
+
+ return __bpf_copy_from_user_mm(dst, dst__sz, unsafe_ptr__ign, mm);
+}
+
+/**
+ * bpf_copy_from_user_mm_str() - Copy a string from an address space
+ * @dst: Destination address, in kernel space. This buffer must be
+ * at least @dst__sz bytes long
+ * @dst__sz: Maximum number of bytes to copy, including the trailing NUL
+ * @unsafe_ptr__ign: Source address in the address space
+ * @mm: Address space to copy from
+ * @flags: The only supported flag is BPF_F_PAD_ZEROS
+ *
+ * Copies a NUL-terminated string from the user address space associated with
+ * @mm. If the string is too long, @dst is still NUL-terminated unless @dst__sz
+ * is zero.
+ *
+ * If the flags are valid and BPF_F_PAD_ZEROS is set, the unused portion of
+ * @dst is cleared on success and all of @dst is cleared on a copy failure.
+ * Unsupported flags return -EINVAL without modifying @dst.
+ *
+ * Return: The number of copied bytes including the NUL terminator on success,
+ * or a negative error code on failure.
+ */
+__bpf_kfunc int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
+ const void __user *unsafe_ptr__ign,
+ struct mm_struct *mm, u64 flags)
+{
+ if (unlikely(flags & ~BPF_F_PAD_ZEROS))
+ return -EINVAL;
+
+ if (unlikely(dst__sz == 0))
+ return 0;
+
+ return __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+ mm, flags);
+}
+
/**
* bpf_copy_from_user_task_str() - Copy a string from an task's address space
* @dst: Destination address, in kernel space. This buffer must be
@@ -3681,6 +3781,7 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
const void __user *unsafe_ptr__ign,
struct task_struct *tsk, u64 flags)
{
+ struct mm_struct *mm;
int ret;
if (unlikely(flags & ~BPF_F_PAD_ZEROS))
@@ -3689,17 +3790,20 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
if (unlikely(dst__sz == 0))
return 0;
- ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 0);
- if (ret < 0) {
+ mm = get_task_mm(tsk);
+ if (!mm) {
if (flags & BPF_F_PAD_ZEROS)
memset(dst, 0, dst__sz);
- return ret;
+ else
+ *(char *)dst = '\0';
+ return -EFAULT;
}
- if (flags & BPF_F_PAD_ZEROS)
- memset(dst + ret, 0, dst__sz - ret);
+ ret = __bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+ mm, flags);
+ mmput(mm);
- return ret + 1;
+ return ret;
}
/* Keep unsigned long in prototype so that kfunc is usable when emitted to
@@ -4924,6 +5028,8 @@ BTF_ID_FLAGS(func, bpf_iter_bits_new, KF_ITER_NEW)
BTF_ID_FLAGS(func, bpf_iter_bits_next, KF_ITER_NEXT | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_iter_bits_destroy, KF_ITER_DESTROY)
BTF_ID_FLAGS(func, bpf_copy_from_user_str, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_mm_str, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_copy_from_user_task_str, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_get_kmem_cache)
BTF_ID_FLAGS(func, bpf_iter_kmem_cache_new, KF_ITER_NEW | KF_SLEEPABLE)
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v6 4/5] bpf: Mark linux_binprm->mm as trusted-or-null
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
` (2 preceding siblings ...)
2026-09-08 13:53 ` [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-09-08 13:53 ` Anastasios Papagiannis
2026-09-08 13:53 ` [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
4 siblings, 0 replies; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:53 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
Mark linux_binprm->mm as a trusted-or-null nested pointer so BPF programs
can pass it to kfuncs after a NULL check.
The field is either NULL or points to a live mm_struct whenever BPF can
access a linux_binprm. On successful exec, exec_mmap() installs the new
address space before begin_new_exec() clears bprm->mm. The bprm_mm_init()
error path clears the field before mmdrop(), and free_bprm() clears it
before mmput(), as ensured by an earlier patch in this series.
Update the existing LSM selftest to check bprm->mm for NULL before
dereferencing it, as required for trusted-or-null pointers.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
Reviewed-by: Sun Jian <sun.jian.kdev@gmail.com>
Reviewed-By: Matt Bobrowski <matt@bobrowski.net>
---
kernel/bpf/verifier.c | 5 +++++
tools/testing/selftests/bpf/progs/lsm.c | 5 ++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 9e79750e2480..791b3d25caa5 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6004,6 +6004,10 @@ BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {
struct inode *d_inode;
};
+BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm) {
+ struct mm_struct *mm;
+};
+
BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket) {
struct sock *sk;
};
@@ -6058,6 +6062,7 @@ static bool type_is_trusted_or_null(struct bpf_verifier_env *env,
{
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct socket));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry));
+ BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct vm_area_struct));
return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id,
diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c
index 7de173daf27b..7441d66c080c 100644
--- a/tools/testing/selftests/bpf/progs/lsm.c
+++ b/tools/testing/selftests/bpf/progs/lsm.c
@@ -113,6 +113,7 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
{
__u32 pid = bpf_get_current_pid_tgid() >> 32;
struct inner_map *inner_map;
+ struct mm_struct *mm;
char args[64];
__u32 key = 0;
__u64 *value;
@@ -121,7 +122,9 @@ int BPF_PROG(test_void_hook, struct linux_binprm *bprm)
bprm_count++;
bpf_copy_from_user(args, sizeof(args), (void *)bprm->vma->vm_mm->arg_start);
- bpf_copy_from_user(args, sizeof(args), (void *)bprm->mm->arg_start);
+ mm = bprm->mm;
+ if (mm)
+ bpf_copy_from_user(args, sizeof(args), (void *)mm->arg_start);
value = bpf_map_lookup_elem(&array, &key);
if (value)
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
` (3 preceding siblings ...)
2026-09-08 13:53 ` [PATCH bpf-next v6 4/5] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
@ 2026-09-08 13:53 ` Anastasios Papagiannis
2026-09-08 16:18 ` bot+bpf-ci
4 siblings, 1 reply; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-08 13:53 UTC (permalink / raw)
To: bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis
Add a sleepable BPF LSM program attached to bprm_check_security to test
bpf_copy_from_user_mm() and bpf_copy_from_user_mm_str() on CONFIG_MMU
kernels.
Starting at bprm->p, verify that bpf_copy_from_user_mm() can copy the
contiguous NUL-separated argument and environment data. Then use
bpf_copy_from_user_mm_str() to read each argument and environment string
separately, advancing the offset by the length returned from each call.
Skip the test on !CONFIG_MMU. In that configuration, exec argument and
environment strings remain in bprm->page[] until the binary loader
transfers them to the new process stack, so they are not accessible
through bprm->mm at the bprm_check_security hook.
Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
.../bpf/prog_tests/copy_from_user_bprm.c | 72 ++++++++++
.../selftests/bpf/progs/copy_from_user_bprm.c | 123 ++++++++++++++++++
2 files changed, 195 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
create mode 100644 tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
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 000000000000..b2325b193576
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/copy_from_user_bprm.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <errno.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <test_progs.h>
+
+#include "copy_from_user_bprm.skel.h"
+
+void test_copy_from_user_bprm(void)
+{
+ char arg0[] = "first";
+ char arg1[] = "second-argument";
+ char env0[] = "SOME_ENV=a";
+ char env1[] = "OTHER_ENV=something";
+ struct copy_from_user_bprm *skel;
+ pid_t child;
+ int status;
+
+ skel = copy_from_user_bprm__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ /*
+ * On !CONFIG_MMU, exec strings are held in bprm->page[] rather than
+ * being mapped in bprm->mm.
+ */
+ if (!skel->kconfig->CONFIG_MMU) {
+ printf("%s:SKIP: test requires CONFIG_MMU\n", __func__);
+ test__skip();
+ goto out;
+ }
+
+ if (!ASSERT_OK(copy_from_user_bprm__attach(skel), "attach"))
+ goto out;
+
+ child = fork();
+ if (!ASSERT_GE(child, 0, "fork"))
+ goto out;
+
+ if (!child) {
+ char *const argv[] = { arg0, arg1, NULL };
+ char *const envp[] = { env0, env1, NULL };
+
+ skel->bss->monitored_pid = getpid();
+ execve("/bin/true", argv, envp);
+ _exit(errno);
+ }
+
+ if (!ASSERT_EQ(waitpid(child, &status, 0), child, "waitpid"))
+ goto out;
+
+ if (ASSERT_TRUE(WIFEXITED(status), "child_exited"))
+ ASSERT_EQ(WEXITSTATUS(status), EPERM, "exec_errno");
+
+ ASSERT_EQ(skel->bss->bprm_argc, 2, "bprm_argc");
+ ASSERT_EQ(skel->bss->bprm_envc, 2, "bprm_envc");
+ ASSERT_EQ(skel->bss->data_len_match, 1, "data_len_match");
+ ASSERT_EQ(skel->bss->invalid_flags_ret, -EINVAL, "invalid_flags_ret");
+ ASSERT_EQ(skel->bss->copy_ret, 0, "copy_ret");
+ ASSERT_EQ(skel->bss->str_arg0_ret, sizeof(arg0), "str_arg0_ret");
+ ASSERT_EQ(skel->bss->str_arg1_ret, sizeof(arg1), "str_arg1_ret");
+ ASSERT_EQ(skel->bss->str_env0_ret, sizeof(env0), "str_env0_ret");
+ ASSERT_EQ(skel->bss->str_env1_ret, sizeof(env1), "str_env1_ret");
+ ASSERT_EQ(skel->bss->data_match, 1, "data_match");
+ ASSERT_EQ(skel->bss->str_args_match, 1, "str_args_match");
+ ASSERT_EQ(skel->bss->str_envs_match, 1, "str_envs_match");
+
+out:
+ copy_from_user_bprm__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c b/tools/testing/selftests/bpf/progs/copy_from_user_bprm.c
new file mode 100644
index 000000000000..b334a157419e
--- /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;
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference
2026-09-08 13:52 ` [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
@ 2026-09-08 14:06 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-09-08 14:06 UTC (permalink / raw)
To: Anastasios Papagiannis; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch clears `bprm->mm` to prevent a BPF Use-After-Free, but misses `bprm->file`, which is also explicitly marked as a trusted pointer by the BPF verifier and is left dangling.
--
commit 1aab8d32effa684f8b1839f59b3ec045d0ba2d6b
Author: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
exec: Clear bprm->mm before dropping its reference
The patch ensures that once mmput() drops the final reference to bprm->mm,
the pointer is no longer accessible through struct linux_binprm. This prevents
BPF kfuncs from accessing a freed mm_struct.
> 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);
> }
[Severity: High]
This isn't a bug introduced by this patch, but while attempting to fix the BPF
use-after-free for bprm->mm, could this leave a dangling pointer in bprm->file
further down in free_bprm()?
In free_bprm(), the reference to bprm->file is dropped, but the pointer is not
cleared:
static void free_bprm(struct linux_binprm *bprm)
{
...
do_close_execat(bprm->file);
/* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */
bprm_drop_loader(bprm);
...
}
Immediately after, the globally visible bprm_drop_loader(bprm) is called.
If a BPF fentry tracing program is attached to bprm_drop_loader, it receives
bprm as a trusted argument. Since the BPF verifier explicitly marks
bprm->file as BTF_TYPE_SAFE_TRUSTED, this exposes the stale pointer to the
tracing program as a trusted argument.
This could allow a use-after-free or direct memory load of a freed struct
file if the program passes this dangling pointer to kfuncs requiring a trusted
struct file *.
Would it be safer to also clear bprm->file after closing it here, similar to
how bprm->mm is being handled?
Note that fields like cred, user_ns, and old_mm do not have this issue because
they are not marked as trusted by the verifier and are treated as untrusted
PTR_TO_BTF_ID pointers.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908135302.74963-1-tasos.papagiannnis@gmail.com?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str()
2026-09-08 13:52 ` [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str() Anastasios Papagiannis
@ 2026-09-08 14:07 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 11+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-08 14:07 UTC (permalink / raw)
To: Anastasios Papagiannis, bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, akpm, andrii, ast, brauner,
daniel, eddyz87, kpsingh, ljs, matt, memxor, song, sun.jian.kdev,
utilityemal77, viro
On 9/8/26 15:52, Anastasios Papagiannis wrote:
> copy_remote_vm_str() gets the target address space from a struct
> task_struct. This does not work for an address space that exists but is
> not yet associated with a task_struct, such as the mm held by struct
> linux_binprm during exec.
>
> Add copy_remote_mm_str(), which operates directly on a struct mm_struct.
>
> Use a common internal interface for the MMU and NOMMU implementations
> and define both public wrappers in mm/util.c. Preserve the existing
> copy_remote_vm_str() behavior, including handling zero-length requests
> before acquiring the task's mm.
>
> Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
> Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> include/linux/mm.h | 8 +++---
> mm/internal.h | 3 +++
> mm/memory.c | 41 ++----------------------------
> mm/nommu.c | 41 ++----------------------------
> mm/util.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 73 insertions(+), 82 deletions(-)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index dd09c438fa23..6f10ce315eaa 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3325,10 +3325,10 @@ extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
> extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
> void *buf, int len, unsigned int gup_flags);
>
> -#ifdef CONFIG_BPF_SYSCALL
> -extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
> - void *buf, int len, unsigned int gup_flags);
> -#endif
> +int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
> + void *buf, int len, unsigned int gup_flags);
> +int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
> + void *buf, int len, unsigned int gup_flags);
Two nits I didn't realize earlier ...
We use two-tab indent on the second parameter line in MM.
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct
2026-09-08 13:53 ` [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-09-08 16:18 ` bot+bpf-ci
2026-09-09 7:24 ` Anastasios Papagiannis
0 siblings, 1 reply; 11+ messages in thread
From: bot+bpf-ci @ 2026-09-08 16:18 UTC (permalink / raw)
To: tasos.papagiannnis, bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3078 bytes --]
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index b3cc5c8fc875..3338bebdd86e 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -679,9 +679,44 @@ const struct bpf_func_proto bpf_copy_from_user_proto = {
> .arg3_type = ARG_ANYTHING,
> };
>
> +static int __bpf_copy_from_user_mm(void *dst, u32 size,
> + const void __user *user_ptr,
> + struct mm_struct *mm)
> +{
> + int ret;
> +
> + ret = access_remote_vm(mm, (unsigned long)user_ptr, dst, size, 0);
Does this drop the address-wrap guard on !CONFIG_MMU builds?
The refactor replaces access_process_vm() with access_remote_vm(). On
CONFIG_MMU these are equivalent, but on !CONFIG_MMU they differ.
The old path through access_process_vm() (mm/nommu.c) includes this guard:
int access_process_vm(struct task_struct *tsk, unsigned long addr,
void *buf, int len, unsigned int gup_flags)
{
struct mm_struct *mm;
if (addr + len < addr) /* wrap guard */
return 0;
mm = get_task_mm(tsk);
...
}
The new path through access_remote_vm() (mm/nommu.c) has no guard:
int access_remote_vm(struct mm_struct *mm, unsigned long addr,
void *buf, int len, unsigned int gup_flags)
{
return __access_remote_vm(mm, addr, buf, len, gup_flags);
}
In __access_remote_vm, the length clamp uses the same wrapped addition:
vma = find_vma(mm, addr);
if (vma) {
if (addr + len >= vma->vm_end)
len = vma->vm_end - addr; /* skipped when wrapped */
...
copy_from_user_page(vma, NULL, addr, buf, (void *) addr, len);
When addr + len wraps to a small value, the check is false, len stays
unclamped, and copy_from_user_page() (a plain memcpy on nommu) reads past
vma->vm_end and past the end of the address space, causing memory
disclosure or a bus fault.
This affects bpf_copy_from_user_task() (which previously had the guard),
and the two new kfuncs bpf_copy_from_user_mm() and
bpf_copy_from_user_mm_str(), since all three call __bpf_copy_from_user_mm().
The address comes directly from the BPF program with no validation on this
path.
The sibling string helper __bpf_copy_from_user_mm_str() does keep the
check via copy_remote_mm_str(), which calls __copy_remote_mm_str() in
mm/nommu.c and uses check_add_overflow(). Only the non-string path lost
it.
This also contradicts the changelog claim "No behavior change is intended
for the existing task-based interfaces" - on !CONFIG_MMU, the behavior of
bpf_copy_from_user_task() does change (a wrapping address previously
returned 0, now reaches the unclamped read).
> + if (ret == size)
> + return 0;
> +
> + memset(dst, 0, size);
> + /* Return -EFAULT for partial read */
> + return ret < 0 ? ret : -EFAULT;
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34245694915
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm
2026-09-08 13:53 ` [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
@ 2026-09-08 16:18 ` bot+bpf-ci
0 siblings, 0 replies; 11+ messages in thread
From: bot+bpf-ci @ 2026-09-08 16:18 UTC (permalink / raw)
To: tasos.papagiannnis, bpf
Cc: linux-fsdevel, linux-kernel, linux-mm, david, akpm, andrii, ast,
brauner, daniel, eddyz87, kpsingh, ljs, matt, memxor, song,
sun.jian.kdev, utilityemal77, viro, tasos.papagiannnis, ast,
andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2729 bytes --]
> 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 000000000000..b2325b193576
> --- /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"
[ ... ]
> + 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);
> + }
This isn't a bug, but would execvp("true", ...) like test_lsm.c does be
more portable here than the hardcoded /bin/true? With the absolute path,
hosts where only /usr/bin/true exists would see execve() fail with ENOENT.
[ ... ]
> 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 000000000000..b334a157419e
> --- /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"
This isn't a bug, but is anything from bpf_misc.h actually needed here,
or could that include be dropped? The test-tag macros, __sink, the
__clobber_* and __imm_* helpers, SYS_PREFIX and CAP_* defines all appear
unreferenced; __ksym, __kconfig and __weak come from bpf/bpf_helpers.h,
u32/u64 and BPF_F_PAD_ZEROS from vmlinux.h, and BPF_PROG from
bpf/bpf_tracing.h.
[ ... ]
> + 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);
This isn't a bug, but could the first call be wrapped the same way as
the copy_ret call just below, so both fit in 80 columns? The first
bpf_copy_from_user_mm() call breaks after the single data argument and
pushes the remaining four arguments onto a 92-column continuation line,
while the identical call three lines below is wrapped conventionally at
the last argument that fits.
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34245694915
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct
2026-09-08 16:18 ` bot+bpf-ci
@ 2026-09-09 7:24 ` Anastasios Papagiannis
0 siblings, 0 replies; 11+ messages in thread
From: Anastasios Papagiannis @ 2026-09-09 7:24 UTC (permalink / raw)
To: bot+bpf-ci
Cc: akpm, andrii, ast, bpf, brauner, daniel, david, eddyz87,
ihor.solodrai, kpsingh, linux-fsdevel, linux-kernel, linux-mm,
ljs, martin.lau, mason, matt, memxor, song, sun.jian.kdev,
tasos.papagiannnis, utilityemal77, viro, yonghong.song
> Does this drop the address-wrap guard on !CONFIG_MMU builds?
This is fixed in https://lore.kernel.org/bpf/20260909064231.18693-1-tasos.papagiannnis@gmail.com/
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-09-09 7:25 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 13:52 [PATCH bpf-next v6 0/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-09-08 13:52 ` [PATCH bpf-next v6 1/5] mm: Add copy_remote_mm_str() Anastasios Papagiannis
2026-09-08 14:07 ` David Hildenbrand (Arm)
2026-09-08 13:52 ` [PATCH bpf-next v6 2/5] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
2026-09-08 14:06 ` sashiko-bot
2026-09-08 13:53 ` [PATCH bpf-next v6 3/5] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-09-08 16:18 ` bot+bpf-ci
2026-09-09 7:24 ` Anastasios Papagiannis
2026-09-08 13:53 ` [PATCH bpf-next v6 4/5] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
2026-09-08 13:53 ` [PATCH bpf-next v6 5/5] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
2026-09-08 16:18 ` bot+bpf-ci
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox