BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct
@ 2026-08-31  9:22 Anastasios Papagiannis
  2026-08-31  9:22 ` [PATCH bpf-next v3 1/7] mm: Add copy_remote_mm_str() Anastasios Papagiannis
                   ` (6 more replies)
  0 siblings, 7 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:22 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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 eBPF user memory helpers
only support reading from the old address space (i.e. current process),
and for this reason 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.

Changing bprm->mm to trusted-or-null would otherwise reject existing BPF
programs that read through it without a NULL check. Preserve that behavior
by allowing fault-protected reads through trusted-or-null BTF pointers.
Pointer arithmetic, writes, and passing the pointer to a kfunc that
requires a non-NULL trusted argument continue to require an explicit
NULL check.

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 also adds selftests covering both kfuncs when reading argument
and environment strings, and verifier tests covering trusted-or-null BTF
pointer reads.

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.

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 (7):
  mm: Add copy_remote_mm_str()
  bpf: Add user memory access kfuncs for mm_struct
  exec: Clear bprm->mm before dropping its reference
  bpf: Allow reads through trusted-or-null BTF pointers
  selftests/bpf: Cover trusted-or-null BTF pointer reads
  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/bpf_verifier.h                  |   9 +-
 include/linux/mm.h                            |   2 +
 kernel/bpf/helpers.c                          | 115 ++++++++++++++--
 kernel/bpf/verifier.c                         |  17 ++-
 mm/memory.c                                   |  33 ++++-
 mm/nommu.c                                    |  33 ++++-
 .../selftests/bpf/prog_tests/bpf_iter.c       |   6 +-
 .../bpf/prog_tests/copy_from_user_bprm.c      |  72 ++++++++++
 .../prog_tests/test_struct_ops_maybe_null.c   |  12 +-
 .../bpf/prog_tests/tp_btf_nullable.c          |  28 ++++
 .../selftests/bpf/progs/copy_from_user_bprm.c | 123 ++++++++++++++++++
 .../selftests/bpf/progs/raw_tp_null_fail.c    |  63 ++++++++-
 .../bpf/progs/test_tp_btf_nullable.c          |  17 ++-
 .../bpf/progs/test_tp_btf_nullable_runtime.c  |  35 +++++
 .../selftests/bpf/progs/verifier_lsm.c        |  19 ++-
 .../selftests/bpf/progs/verifier_vfs_accept.c |  13 ++
 .../selftests/bpf/progs/verifier_vfs_reject.c |  14 --
 .../selftests/bpf/test_kmods/bpf_testmod.c    |   1 +
 .../testing/selftests/sched_ext/maybe_null.c  |   6 +-
 20 files changed, 566 insertions(+), 59 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
 create mode 100644 tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c


base-commit: d761934c9483ecde93fe99d8705282f716dfee50
-- 
2.55.0


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH bpf-next v3 1/7] mm: Add copy_remote_mm_str()
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-08-31  9:22 ` Anastasios Papagiannis
  2026-08-31  9:23 ` [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:22 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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.

Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
 include/linux/mm.h |  2 ++
 mm/memory.c        | 33 +++++++++++++++++++++++++++++----
 mm/nommu.c         | 33 +++++++++++++++++++++++++++++----
 3 files changed, 60 insertions(+), 8 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 485df9c2dbdd..eede435bf4a3 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3222,6 +3222,8 @@ extern int access_remote_vm(struct mm_struct *mm, unsigned long addr,
 		void *buf, int len, unsigned int gup_flags);
 
 #ifdef CONFIG_BPF_SYSCALL
+extern int copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
+			      void *buf, int len, unsigned int gup_flags);
 extern int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 			      void *buf, int len, unsigned int gup_flags);
 #endif
diff --git a/mm/memory.c b/mm/memory.c
index 6b8280cfc1db..4c5f0b629889 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7141,12 +7141,15 @@ 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,
+static 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;
 
+	if (unlikely(len == 0))
+		return 0;
+
 	*(char *)buf = '\0';
 
 	if (mmap_read_lock_killable(mm))
@@ -7218,6 +7221,27 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return buf - old_buf;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. 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)
+{
+	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
@@ -7229,8 +7253,9 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
  * 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.
+ * 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)
@@ -7247,7 +7272,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len, gup_flags);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
diff --git a/mm/nommu.c b/mm/nommu.c
index ed3934bc2de4..94e3709e95fd 100644
--- a/mm/nommu.c
+++ b/mm/nommu.c
@@ -1716,13 +1716,16 @@ 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,
+static int __copy_remote_mm_str(struct mm_struct *mm, unsigned long addr,
 				void *buf, int len)
 {
 	unsigned long addr_end;
 	struct vm_area_struct *vma;
 	int ret = -EFAULT;
 
+	if (unlikely(len == 0))
+		return 0;
+
 	*(char *)buf = '\0';
 
 	if (mmap_read_lock_killable(mm))
@@ -1752,6 +1755,27 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
 	return ret;
 }
 
+/**
+ * copy_remote_mm_str - copy a string from a remote address space.
+ * @mm:         the remote address space
+ * @addr:       start address to read from
+ * @buf:        destination buffer
+ * @len:        number of bytes to copy
+ * @gup_flags:  flags modifying lookup behaviour (unused)
+ *
+ * The caller must hold a reference on @mm.
+ *
+ * Return: number of bytes copied from @addr (source) to @buf (destination),
+ * not including the trailing NUL. 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)
+{
+	return __copy_remote_mm_str(mm, addr, buf, len);
+}
+
 /**
  * copy_remote_vm_str - copy a string from another process's address space.
  * @tsk:	the task of the target address space
@@ -1763,8 +1787,9 @@ static int __copy_remote_vm_str(struct mm_struct *mm, unsigned long addr,
  * 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.
+ * 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)
@@ -1781,7 +1806,7 @@ int copy_remote_vm_str(struct task_struct *tsk, unsigned long addr,
 		return -EFAULT;
 	}
 
-	ret = __copy_remote_vm_str(mm, addr, buf, len);
+	ret = copy_remote_mm_str(mm, addr, buf, len, gup_flags);
 
 	mmput(mm);
 
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
  2026-08-31  9:22 ` [PATCH bpf-next v3 1/7] mm: Add copy_remote_mm_str() Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  2026-08-31 10:01   ` sashiko-bot
  2026-08-31 10:43   ` bot+bpf-ci
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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 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 and mark them KF_SLEEPABLE because accessing the
remote address space can fault.

On !CONFIG_MMU, exec argument and environment strings remain in
bprm->page[] until the binary loader transfers them to the new process
stack. They are therefore not accessible through bprm->mm at the
bprm_check_security hook. The linux_binprm use described above is
CONFIG_MMU-only, although the new kfuncs remain available for other
address spaces on !CONFIG_MMU.

Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
 kernel/bpf/helpers.c | 115 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 101 insertions(+), 14 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc875..19c01aa734ae 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -32,6 +32,13 @@
 
 #include "../../lib/kstrtox.h"
 
+int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
+			  const void __user *unsafe_ptr__ign,
+			  struct mm_struct *mm, u64 flags);
+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 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 +689,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 = {
@@ -3680,6 +3680,83 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user
 __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;
+
+	mm = get_task_mm(tsk);
+	ret = bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
+					mm, flags);
+	if (mm)
+		mmput(mm);
+
+	return ret;
+}
+
+/**
+ * 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;
 
@@ -3689,7 +3766,15 @@ __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 (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);
@@ -4924,6 +5009,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] 19+ messages in thread

* [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
  2026-08-31  9:22 ` [PATCH bpf-next v3 1/7] mm: Add copy_remote_mm_str() Anastasios Papagiannis
  2026-08-31  9:23 ` [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  2026-08-31 10:25   ` sashiko-bot
                     ` (3 more replies)
  2026-08-31  9:23 ` [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers Anastasios Papagiannis
                   ` (3 subsequent siblings)
  6 siblings, 4 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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 eBPF 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>
---
 fs/exec.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index a14f28b15607..4b4d8f0627a7 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1453,9 +1453,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] 19+ messages in thread

* [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
                   ` (2 preceding siblings ...)
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  2026-08-31 10:43   ` bot+bpf-ci
  2026-08-31  9:23 ` [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads Anastasios Papagiannis
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis

Currently, a trusted-or-null pointer
(i.e. PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL) has to be checked
for NULL before it can be dereferenced. Marking a field from
PTR_TO_BTF_ID typing to trusted-or-null can reject programs that
previously dereferenced the pointer directly. This is useful as we
need to mark new fields as trusted in order to pass those as arguments
to kfuncs.

This patch adds support to dereference pointers marked as
PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL without the need for a NULL
check. We define that those pointers can fault on dereference, so
reads need to happen through BPF_PROBE_MEM. If the dereferenced pointer
is another BTF pointer, we clear trusted flags and mark the result as
PTR_UNTRUSTED.

This applies only to reads. Other cases still require an explicit NULL
check. After such a check, the pointer retains PTR_TRUSTED and can be
used normally.

To summarise, before that patch, reads to PTR_TO_BTF_ID|PTR_UNTRUSTED
are allowed through BPF_PROBE_MEM and reads to
PTR_TO_BTF_ID|PTR_TRUSTED are normal loads. This patch allows
PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL to happen through
BPF_PROBE_MEM.

The unchecked read path has two consequences:

1. It uses BPF_PROBE_MEM, which is slower than a normal load. An
   explicit NULL check refines the pointer to PTR_TRUSTED and allows a
   normal load.

2. A faulting read returns zero, which is indistinguishable from a
   legitimately zero-valued field. Programs that need to distinguish
   those cases must check the pointer before reading the field.

The next patch updates current tests and also introduces more checks to
ensure this change does not break anything.

Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
 include/linux/bpf_verifier.h |  9 ++++++++-
 kernel/bpf/verifier.c        | 12 +++++++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index ae9f606539f4..d3d7254cc1f6 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1326,6 +1326,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)
 {
 	/*
@@ -1333,7 +1338,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/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8f585ceb2cd5..386401fe051d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6116,6 +6116,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.
@@ -6591,7 +6600,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) {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
                   ` (3 preceding siblings ...)
  2026-08-31  9:23 ` [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  2026-08-31 10:43   ` bot+bpf-ci
  2026-08-31 10:55   ` sashiko-bot
  2026-08-31  9:23 ` [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
  2026-08-31  9:23 ` [PATCH bpf-next v3 7/7] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
  6 siblings, 2 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis

Update verifier tests that expected an unchecked trusted-or-null BTF
pointer dereference to fail. Cover scalar reads and chained reads through
BTF and memory pointers. Add a runtime test which verifies that non-NULL
reads return the field value and NULL reads return zero.

Verify that pointer arithmetic, stores, atomic RMW operations, and
BPF_LOAD_ACQ accesses remain prohibited. Assert that a BTF pointer
derived from an unchecked trusted-or-null load is PTR_UNTRUSTED. Verify
that attempting to NULL-check the derived pointer remains rejected and
that it cannot be passed to a kfunc requiring an RCU pointer. Keep the
existing NULL-check tests to verify that an explicit check of the original
pointer recovers normal trusted pointer behavior.

Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
 .../selftests/bpf/prog_tests/bpf_iter.c       |  6 +-
 .../prog_tests/test_struct_ops_maybe_null.c   | 12 ++--
 .../bpf/prog_tests/tp_btf_nullable.c          | 28 +++++++++
 .../selftests/bpf/progs/raw_tp_null_fail.c    | 63 ++++++++++++++++++-
 .../bpf/progs/test_tp_btf_nullable.c          | 17 ++++-
 .../bpf/progs/test_tp_btf_nullable_runtime.c  | 35 +++++++++++
 .../selftests/bpf/progs/verifier_lsm.c        | 19 ++++--
 .../selftests/bpf/progs/verifier_vfs_accept.c | 13 ++++
 .../selftests/bpf/progs/verifier_vfs_reject.c | 14 -----
 .../selftests/bpf/test_kmods/bpf_testmod.c    |  1 +
 .../testing/selftests/sched_ext/maybe_null.c  |  6 +-
 11 files changed, 181 insertions(+), 33 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_tp_btf_nullable_runtime.c

diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_iter.c b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
index c69080ca14f5..99a16a1add70 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/test_struct_ops_maybe_null.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_maybe_null.c
index 01dc2613c8a5..03670ddc89bd 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,15 @@ 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 +41,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 accc42e01f8a..825fe7a92d74 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/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
index 0d58114a4955..45df28c0a25e 100644
--- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -2,14 +2,18 @@
 /* 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";
 
+extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
+extern void bpf_task_release(struct task_struct *p) __ksym;
+
 /* Ensure module parameter has PTR_MAYBE_NULL */
 SEC("tp_btf/bpf_testmod_test_raw_tp_null_tp")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
 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;
@@ -17,8 +21,63 @@ int test_raw_tp_null_bpf_testmod_test_raw_tp_null_arg_1(void *ctx) {
 
 /* Check NULL marking */
 SEC("tp_btf/sched_pi_setprio")
-__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
+__success
 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;
 }
+
+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 cf0547a613ff..a86b526d27f9 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,19 @@ 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;
+}
+
 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 000000000000..5c9c7f94040d
--- /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 c724bf389f5c..c912215579c9 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,17 @@ int BPF_PROG(null_check, struct file *file)
 	return 0;
 }
 
+SEC("lsm.s/bprm_check_security")
+__description("store through trusted-or-null bprm->mm is rejected")
+__failure
+__msg("R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(store_through_trusted_or_null_bprm_mm,
+	     struct linux_binprm *bprm)
+{
+	bprm->mm->task_size = 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 55398c04290a..2827a1650727 100644
--- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
+++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
@@ -100,4 +100,17 @@ int BPF_PROG(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
 	return 0;
 }
 
+SEC("lsm.s/inode_rename")
+__success
+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 8f0c45421f89..2a0813258183 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 2380b6cbdead..2da9c2464c25 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -651,6 +651,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.c b/tools/testing/selftests/sched_ext/maybe_null.c
index aacf0c58ca4f..8fec296c31bd 100644
--- a/tools/testing/selftests/sched_ext/maybe_null.c
+++ b/tools/testing/selftests/sched_ext/maybe_null.c
@@ -25,11 +25,11 @@ static enum scx_test_status run(void *ctx)
 	maybe_null__destroy(skel);
 
 	fail_dsp = maybe_null_fail_dsp__open_and_load();
-	if (fail_dsp) {
-		maybe_null_fail_dsp__destroy(fail_dsp);
-		SCX_ERR("Should failed to open and load maybe_null_fail_dsp skel");
+	if (!fail_dsp) {
+		SCX_ERR("Failed to open and load maybe_null_fail_dsp skel");
 		return SCX_TEST_FAIL;
 	}
+	maybe_null_fail_dsp__destroy(fail_dsp);
 
 	fail_yld = maybe_null_fail_yld__open_and_load();
 	if (fail_yld) {
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
                   ` (4 preceding siblings ...)
  2026-08-31  9:23 ` [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  2026-08-31 10:43   ` bot+bpf-ci
  2026-09-01  3:50   ` sun jian
  2026-08-31  9:23 ` [PATCH bpf-next v3 7/7] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis
  6 siblings, 2 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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 linux_binprm owns this mm
throughout the bprm_check_security hook and clears the pointer only after
exec_mmap() installs the new address space.

Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
---
 kernel/bpf/verifier.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 386401fe051d..584c79326b9c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5958,6 +5958,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;
 };
@@ -6012,6 +6016,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,
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [PATCH bpf-next v3 7/7] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm
  2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
                   ` (5 preceding siblings ...)
  2026-08-31  9:23 ` [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
@ 2026-08-31  9:23 ` Anastasios Papagiannis
  6 siblings, 0 replies; 19+ messages in thread
From: Anastasios Papagiannis @ 2026-08-31  9:23 UTC (permalink / raw)
  To: bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, 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..a0e2d1c86c2c
--- /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] 19+ messages in thread

* Re: [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct
  2026-08-31  9:23 ` [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
@ 2026-08-31 10:01   ` sashiko-bot
  2026-08-31 10:43   ` bot+bpf-ci
  1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-08-31 10:01 UTC (permalink / raw)
  To: Anastasios Papagiannis; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `dst` arguments in `bpf_copy_from_user_mm` and `bpf_copy_from_user_mm_str` lack the `__uninit` suffix, causing the verifier to reject uninitialized stack memory and forcing unnecessary zero-initialization.
--

commit 064bec09b54d9a7638a0865baa943bd0e6467f30
Author: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>

bpf: Add user memory access kfuncs for mm_struct

This commit adds bpf_copy_from_user_mm() and bpf_copy_from_user_mm_str()
kfuncs to allow BPF programs to access trusted address spaces not associated
with a task_struct. It also updates existing task-based user memory access
helpers to use these new functions.

> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index b3cc5c8fc8756..19c01aa734ae1 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c

[ ... ]

> +__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)
> +{

[Severity: Low]
Should the dst parameter be named dst__uninit here?

Without the __uninit suffix, the BPF verifier natively treats void pointer
kfunc parameters as ARG_PTR_TO_MEM. This forces BPF programs to redundantly
zero-initialize their buffers before calling these sleepable kfuncs, degrading
usability and performance compared to the older bpf_copy_from_user_task helper.

> +	int ret;
> +
> +	if (unlikely(flags))
> +		return -EINVAL;

[Severity: Low]
If the __uninit suffix is added, the verifier will assume the buffer is
initialized upon return. Without clearing the buffer on this early path,
could this allow a BPF program to read uninitialized kernel stack memory?

[ ... ]

> +__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;

[Severity: Low]
Similar to bpf_copy_from_user_mm above, does this dst parameter also need the
__uninit suffix to avoid forcing BPF programs to pre-initialize the memory?

If it does, the early flags check in this function would also need to zero
the buffer before returning -EINVAL to avoid leaking uninitialized memory.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=2

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
@ 2026-08-31 10:25   ` sashiko-bot
  2026-08-31 10:30   ` bot+bpf-ci
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-08-31 10:25 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] Use-After-Free of `bprm->file` via eBPF kfuncs due to dangling trusted pointer.
--

commit ac94f9ff4f270354d3714d4162d6adfa946a8fdf
Author: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>

exec: Clear bprm->mm before dropping its reference

This commit clears bprm->mm before calling mmput() in free_bprm().
This ensures that eBPF programs cannot access a dangling mm_struct
pointer when hooking into execution paths.

> diff --git a/fs/exec.c b/fs/exec.c
> index a14f28b15607a..4b4d8f0627a7c 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1453,9 +1453,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);

[Severity: High]
This is a pre-existing issue, but since this patch addresses dangling
pointers in free_bprm() for eBPF, does bprm->file need similar handling?

Further down in free_bprm(), the code drops the file reference and then
calls a hookable function:

	do_close_execat(bprm->file);
	/* An unconsumed PT_INTERP substitute from a binfmt_misc loader entry. */
	bprm_drop_loader(bprm);

When kernel_execve() fails in a kernel thread, free_bprm() is called.
The do_close_execat() call drops the final reference to the file.
Because this executes in a kernel thread (PF_KTHREAD), fput() queues
the file for asynchronous freeing via delayed_fput_work.

Right after this, bprm_drop_loader() is called. If an eBPF fentry program
is attached to bprm_drop_loader, it receives the bprm argument.

Because the BPF verifier defines the file pointer within linux_binprm as
BTF_TYPE_SAFE_TRUSTED, the verifier trusts the pointer. The eBPF program
can read bprm->file and pass it to a kfunc like bpf_get_file_xattr().

Could this result in a use-after-free if the kfunc dereferences the file
pointer concurrently while the workqueue frees it?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=3

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
  2026-08-31 10:25   ` sashiko-bot
@ 2026-08-31 10:30   ` bot+bpf-ci
  2026-09-01  3:19   ` sun jian
  2026-09-01  8:56   ` sun jian
  3 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-08-31 10:30 UTC (permalink / raw)
  To: tasos.papagiannnis, bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1392 bytes --]

> commit d3633d366b08f6123fe20f3fd64fd2a8c5f6a5af
> Author: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
> Date:   Mon Aug 31 12:23:01 2026 +0300
>
>     exec: Clear bprm->mm before dropping its reference
>
>     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 eBPF 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>

This looks like a bug fix. Should this include a Fixes: tag?

The dangling pointer bug was introduced when eBPF kfuncs were added that
access bprm->mm. At that point, bprm->mm needed to be either NULL or
point to valid memory, but free_bprm() wasn't updated to clear bprm->mm
before calling mmput().

Suggested:

  Fixes: ef5769fc8b1e ("bpf: Add user memory access kfuncs for mm_struct")


---
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/33379004067

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null
  2026-08-31  9:23 ` [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
@ 2026-08-31 10:43   ` bot+bpf-ci
  2026-09-01  3:50   ` sun jian
  1 sibling, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-08-31 10:43 UTC (permalink / raw)
  To: tasos.papagiannnis, bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 4419 bytes --]

> bpf: Mark linux_binprm->mm as trusted-or-null
>
> Mark linux_binprm->mm as a trusted-or-null nested pointer so BPF programs
> can pass it to kfuncs after a NULL check. The linux_binprm owns this mm
> throughout the bprm_check_security hook and clears the pointer only after
> exec_mmap() installs the new address space.

Is the changelog's description of when bprm->mm is cleared accurate?

Enumerating every write to bprm->mm in the tree shows three places where
the pointer is cleared, not just "after exec_mmap()":

  fs/exec.c:bprm_mm_init()
      bprm->mm = mm = mm_alloc();          /* assign */
      ...
      bprm->mm = NULL;                     /* error path, before mmdrop */

  fs/exec.c:begin_new_exec()
      bprm->mm = NULL;                     /* after successful exec_mmap() */

  fs/exec.c:free_bprm()
      bprm->mm = NULL;                     /* before mmput() */

Two of these three are not accounted for by the changelog. The
bprm_mm_init() error path clears the pointer before mmdrop(), and
free_bprm() clears it before mmput(). The free_bprm() case is actually
load-bearing: it's what commit d3633d366b08f ("exec: Clear bprm->mm
before dropping its reference") in this same series was added for, and
that commit's changelog says it's "required for eBPF kfuncs where
bprm->mm is either NULL or points to a live mm_struct."

The real invariant this verifier change depends on is "bprm->mm is
always either NULL or a live mm," established across all of fs/exec.c.
The changelog instead asserts a narrower property that doesn't hold.

This matters because the marking isn't scoped to bprm_check_security.
Any BPF program with a trusted linux_binprm pointer (all five bprm_*
LSM hooks in include/linux/lsm_hook_defs.h, plus fentry/fexit/tp_btf on
any kernel function taking a linux_binprm) now gets a trusted mm.
Justifying it from one hook leaves a reviewer unable to check the other
attach points.

Could the changelog drop "only" and state the actual invariant, perhaps
something like: "bprm->mm is either NULL or points to a live mm_struct
at every point a BPF program can observe a linux_binprm: exec_mmap()
transfers ownership and begin_new_exec() then clears the field, and
free_bprm() clears it before dropping the reference (see the preceding
patch)"?

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 386401fe051d4..584c79326b9c7 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c

[ ... ]

> @@ -6012,6 +6016,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,

Does the patch ordering break bisect here?

The preceding commit in the series, 146a2bd992c3d ("selftests/bpf: Cover
trusted-or-null BTF pointer reads"), adds a test in
tools/testing/selftests/bpf/progs/verifier_lsm.c that expects this
marking to exist:

  SEC("lsm.s/bprm_check_security")
  __description("store through trusted-or-null bprm->mm is rejected")
  __failure
  __msg("R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'")
  int BPF_PROG(store_through_trusted_or_null_bprm_mm,
               struct linux_binprm *bprm)
  {
          bprm->mm->task_size = 0;
          return 0;
  }

At commit 146a2bd992c3d, walking bprm->mm in a sleepable LSM program
takes the final else arm of check_ptr_to_btf_access() (flag =
PTR_UNTRUSTED) because in_rcu_cs() is false. The subsequent store fails
at the 'if (atype != BPF_READ && bpf_may_fault_on_deref(reg->type))'
check with "only read is supported" rather than "invalid mem access
'trusted_ptr_or_null_'". The __msg() assertion doesn't match and
'test_progs -t verifier_lsm' fails at that intermediate commit.

Swapping the order makes every commit in the series independently
testable. The ordering issue is present in the posted series as well
(lore message-ids 20260831092305.42062-6 and -7).


---
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/33379004067

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct
  2026-08-31  9:23 ` [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
  2026-08-31 10:01   ` sashiko-bot
@ 2026-08-31 10:43   ` bot+bpf-ci
  1 sibling, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-08-31 10:43 UTC (permalink / raw)
  To: tasos.papagiannnis, bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 9220 bytes --]

> bpf: Add user memory access kfuncs for mm_struct
>
> 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 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 and mark them KF_SLEEPABLE because accessing the
> remote address space can fault.
>
> On !CONFIG_MMU, exec argument and environment strings remain in
> bprm->page[] until the binary loader transfers them to the new process
> stack. They are therefore not accessible through bprm->mm at the
> bprm_check_security hook. The linux_binprm use described above is
> CONFIG_MMU-only, although the new kfuncs remain available for other
> address spaces on !CONFIG_MMU.

This isn't a bug, but the changelog describes the patch as adding two
new kfuncs, without noting that it also rewrites two existing entry
points. BPF_CALL_5(bpf_copy_from_user_task) becomes a get_task_mm()
wrapper around bpf_copy_from_user_mm() instead of calling
access_process_vm() directly, and bpf_copy_from_user_task_str()
becomes a wrapper around bpf_copy_from_user_mm_str() instead of
calling copy_remote_vm_str().

Could the changelog note that bpf_copy_from_user_task() and
bpf_copy_from_user_task_str() are refactored into wrappers with no
intended behaviour change?

Also, the closing paragraph describes !CONFIG_MMU bprm->page[]
behaviour that this patch does not implement or depend on. Would that
paragraph sit better in the selftest patch that acts on it?

> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index b3cc5c8fc8756..19c01aa734ae1 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -30,10 +30,17 @@
>  #include <linux/irq_work.h>
>  #include <linux/buildid.h>
>
>  #include "../../lib/kstrtox.h"
>
> +int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
> +			  const void __user *unsafe_ptr__ign,
> +			  struct mm_struct *mm, u64 flags);
> +int bpf_copy_from_user_mm_str(void *dst, u32 dst__sz,
> +			      const void __user *unsafe_ptr__ign,
> +			      struct mm_struct *mm, u64 flags);
> +

This isn't a bug, but these are the only function forward declarations
in kernel/bpf/helpers.c. The first is unavoidable: bpf_copy_from_user_mm()
is called from BPF_CALL_5(bpf_copy_from_user_task) about 650 lines above
its definition. The second is not: bpf_copy_from_user_mm_str() has exactly
one in-file caller, bpf_copy_from_user_task_str(), and the new code defines
the wrapper before the function it wraps.

Could bpf_copy_from_user_task_str() move below bpf_copy_from_user_mm_str()
so only one forward declaration is needed?

Also, both prototypes omit __bpf_kfunc and sit outside the
__bpf_kfunc_start_defs()/__bpf_kfunc_end_defs() region, while the
definitions add __used/__retain/noinline through the macro. Every other
kfunc in the tree is declared and defined with __bpf_kfunc.

Should the remaining prototype carry __bpf_kfunc to match the definition?

>  /* 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

[ ... ]

> @@ -680,26 +687,19 @@ 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;
>  }

[ ... ]

> @@ -3678,20 +3678,105 @@ __bpf_kfunc int bpf_copy_from_user_str(void *dst, u32 dst__sz, const void __user
>   * A negative error code on failure.
>   */
>  __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;
> +
> +	mm = get_task_mm(tsk);
> +	ret = bpf_copy_from_user_mm_str(dst, dst__sz, unsafe_ptr__ign,
> +					mm, flags);
> +	if (mm)
> +		mmput(mm);
> +
> +	return ret;
> +}
> +
> +/**
> + * 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;
>
> -	ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_ptr__ign, dst, dst__sz, 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;
>  	}

[ ... ]

> @@ -4922,10 +5007,12 @@ BTF_ID_FLAGS(func, bpf_preempt_disable)
>  BTF_ID_FLAGS(func, bpf_preempt_enable)
>  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)
>  BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPABLE)
>  BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)


---
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/33379004067

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers
  2026-08-31  9:23 ` [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers Anastasios Papagiannis
@ 2026-08-31 10:43   ` bot+bpf-ci
  0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-08-31 10:43 UTC (permalink / raw)
  To: tasos.papagiannnis, bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2367 bytes --]

> bpf: Allow reads through trusted-or-null BTF pointers
>
> Currently, a trusted-or-null pointer
> (i.e. PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL) has to be checked
> for NULL before it can be dereferenced. Marking a field from
> PTR_TO_BTF_ID typing to trusted-or-null can reject programs that
> previously dereferenced the pointer directly. This is useful as we
> need to mark new fields as trusted in order to pass those as arguments
> to kfuncs.
>
> This patch adds support to dereference pointers marked as
> PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL without the need for a NULL
> check. We define that those pointers can fault on dereference, so
> reads need to happen through BPF_PROBE_MEM. If the dereferenced pointer
> is another BTF pointer, we clear trusted flags and mark the result as
> PTR_UNTRUSTED.
>
> This applies only to reads. Other cases still require an explicit NULL
> check. After such a check, the pointer retains PTR_TRUSTED and can be
> used normally.
>
> To summarise, before that patch, reads to PTR_TO_BTF_ID|PTR_UNTRUSTED
> are allowed through BPF_PROBE_MEM and reads to
> PTR_TO_BTF_ID|PTR_TRUSTED are normal loads. This patch allows
> PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL to happen through
> BPF_PROBE_MEM.
>
> The unchecked read path has two consequences:
>
> 1. It uses BPF_PROBE_MEM, which is slower than a normal load. An
>    explicit NULL check refines the pointer to PTR_TRUSTED and allows a
>    normal load.
>
> 2. A faulting read returns zero, which is indistinguishable from a
>    legitimately zero-valued field. Programs that need to distinguish
>    those cases must check the pointer before reading the field.
>
> The next patch updates current tests and also introduces more checks to
> ensure this change does not break anything.
>
> Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>

This isn't a bug, but could the changelog use the imperative mood here
("Allow reads through PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL without a
NULL check") and drop the "To summarise" paragraph, since the preceding
paragraph already states the before/after behaviour?


---
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/33379004067

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads
  2026-08-31  9:23 ` [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads Anastasios Papagiannis
@ 2026-08-31 10:43   ` bot+bpf-ci
  2026-08-31 10:55   ` sashiko-bot
  1 sibling, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-08-31 10:43 UTC (permalink / raw)
  To: tasos.papagiannnis, bpf
  Cc: linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm, david,
	ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77, tasos.papagiannnis, ast, andrii, daniel,
	martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 10966 bytes --]

> Verify that pointer arithmetic, stores, atomic RMW operations, and
> BPF_LOAD_ACQ accesses remain prohibited. Assert that a BTF pointer
> derived from an unchecked trusted-or-null load is PTR_UNTRUSTED.

Does the patch actually verify atomic RMW operations and BPF_LOAD_ACQ
accesses?

Looking at the diff, pointer arithmetic is covered by
handle_tp_btf_nullable_arith in progs/test_tp_btf_nullable.c with the
expected verifier message "pointer arithmetic on trusted_ptr_or_null_
prohibited". Stores are covered by store_through_trusted_or_null_bprm_mm
in progs/verifier_lsm.c.

But there is no test for an atomic RMW operation (such as
__sync_fetch_and_add through a trusted-or-null pointer) or for a
BPF_LOAD_ACQ access (such as __atomic_load_n with __ATOMIC_ACQUIRE).
Running grep over the diff for "atomic", "load_acq", or "__sync_fetch"
only matches the commit message text itself.

The underlying kernel behaviour is correct: is_load_acq_unsafe() rejects
BPF_LOAD_ACQ on a may-fault pointer, and an atomic RMW is rejected by
the BPF_WRITE pass of check_mem_access(). So this is a coverage gap
rather than a functional bug.

> 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 0d58114a4955c..45df28c0a25ed 100644
> --- a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
> +++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
> @@ -2,14 +2,18 @@
>  /* 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";
>
> +extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
> +extern void bpf_task_release(struct task_struct *p) __ksym;
> +
>  /* Ensure module parameter has PTR_MAYBE_NULL */
>  SEC("tp_btf/bpf_testmod_test_raw_tp_null_tp")
> -__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
> +__success
>  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;

Flipping these two tests from __failure to __success leaves their
comments describing an assertion that no longer exists, and drops the
only compile-time proof of nullability marking for these tracepoint
arguments.

Both programs now load whether or not the argument carries
PTR_MAYBE_NULL: after this series a read through a trusted-or-null
pointer is allowed, and a read through a plain trusted pointer was
always allowed. So if a future change accidentally stopped marking the
module tracepoint argument or the sched_pi_setprio argument as
maybe-null, these __success tests would keep passing while the comments
still claim otherwise. The file name raw_tp_null_fail.c is likewise now
inaccurate for the two leading tests.

Mitigating factors: progs/raw_tp_null.c plus prog_tests/raw_tp_null.c
still prove the module tracepoint argument is maybe-null at runtime via
ASSERT_EQ(skel->bss->i, 3), and the new trusted_or_null_walk_is_untrusted
test indirectly proves the marking for sched_pi_setprio because its
expected verifier message only holds if the argument was trusted-or-null.

Would retaining an explicit assertion (such as __log_level(2) plus
__msg("trusted_ptr_or_null_")) or updating the comments to reflect the
new test semantics preserve the intent?

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
> index c724bf389f5c6..c912215579c92 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c

[ ... ]

> @@ -188,6 +188,17 @@ int BPF_PROG(null_check, struct file *file)
>  	return 0;
>  }
>
> +SEC("lsm.s/bprm_check_security")
> +__description("store through trusted-or-null bprm->mm is rejected")
> +__failure
> +__msg("R{{[0-9]+}} invalid mem access 'trusted_ptr_or_null_'")
> +int BPF_PROG(store_through_trusted_or_null_bprm_mm,
> +	     struct linux_binprm *bprm)
> +{
> +	bprm->mm->task_size = 0;
> +	return 0;
> +}

Can this new negative test actually pass at this commit?

The test relies on bprm->mm being loaded as a trusted-or-null pointer.
But struct linux_binprm is not in the BTF_TYPE_SAFE_TRUSTED_OR_NULL()
allow-list at this commit.

Looking at the list in kernel/bpf/verifier.c (around line 5957), at
146a2bd it contains only struct dentry {d_inode}, struct socket {sk},
and struct vm_area_struct {vm_mm, vm_file}.
BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct linux_binprm {struct mm_struct *mm;})
is only added two commits later, by 83008d7d1f94b ("bpf: Mark
linux_binprm->mm as trusted-or-null") which is outside this commit.

Consequently, in this sleepable (lsm.s) program in_rcu_cs() is false and
neither type_is_trusted() nor type_is_trusted_or_null() matches, so
bprm->mm is walked as PTR_TO_BTF_ID | PTR_UNTRUSTED. The store then hits
the earlier guard in check_ptr_to_btf_access():

    if (atype != BPF_READ && bpf_may_fault_on_deref(reg->type)) {
        verbose(env, "only read is supported\n");
        return -EACCES;
    }

The program is still rejected, so __failure holds, but the log says
"only read is supported" instead of "R<n> invalid mem access
'trusted_ptr_or_null_'". test_loader's __msg match therefore fails and
the verifier_lsm subtest reports failure at this commit and at the
immediately following commit; it only starts passing at 83008d7d1f94b.

Either the linux_binprm marking must be ordered before this selftest
commit, or the test must use a type that is already trusted-or-null here
(such as struct socket {sk} or struct vm_area_struct {vm_mm}).

> diff --git a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
> index 55398c04290a8..2827a16507274 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_vfs_accept.c

[ ... ]

> 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";

Does the deletion of this test leave struct dentry's d_inode field
without a type assertion?

The deleted test was the only place in the tree that asserted struct
dentry's d_inode field is typed PTR_TO_BTF_ID|PTR_TRUSTED|PTR_MAYBE_NULL.
Its __msg("invalid mem access 'trusted_ptr_or_null_'") named the reg
type explicitly, so it failed if d_inode ever stopped being listed in
BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) in kernel/bpf/verifier.c.

The replacement in verifier_vfs_accept.c is a bare __success program:

    SEC("lsm.s/inode_rename")
    __success
    int BPF_PROG(inode_rename_no_null_check, ...)
    {
            ino_t ino = new_dentry->d_inode->i_ino;
            ...
    }

This program loads identically whether d_inode is
PTR_TRUSTED|PTR_MAYBE_NULL (the intended marking, read via BPF_PROBE_MEM),
plain PTR_TRUSTED (normal load), or plain PTR_TO_BTF_ID. The other
d_inode test in the same file (inode_rename at line 88, which
NULL-checks first) is also __success and equally indiscriminate.

A grep of tools/testing/selftests/bpf/progs for d_inode finds no other
type assertion (only local_storage.c and profiler.inc.h, neither of which
checks reg types). So after this patch nothing detects a regression in
the trusted-or-null marking of dentry->d_inode.

Would adding __log_level(2) __msg("R{{[0-9]+}}=trusted_ptr_or_null_inode")
to the new accept test (the style already used by raw_tp_null_fail.c and
verifier_global_ptr_args.c), or keeping a __failure store/arithmetic case
on new_dentry->d_inode, restore the lost discrimination?

[ ... ]

> diff --git a/tools/testing/selftests/sched_ext/maybe_null.c b/tools/testing/selftests/sched_ext/maybe_null.c
> index aacf0c58ca4fa..8fec296c31bd3 100644
> --- a/tools/testing/selftests/sched_ext/maybe_null.c
> +++ b/tools/testing/selftests/sched_ext/maybe_null.c
> @@ -25,11 +25,11 @@ static enum scx_test_status run(void *ctx)
>  	maybe_null__destroy(skel);
>
>  	fail_dsp = maybe_null_fail_dsp__open_and_load();
> -	if (fail_dsp) {
> -		maybe_null_fail_dsp__destroy(fail_dsp);
> -		SCX_ERR("Should failed to open and load maybe_null_fail_dsp skel");
> +	if (!fail_dsp) {
> +		SCX_ERR("Failed to open and load maybe_null_fail_dsp skel");
>  		return SCX_TEST_FAIL;
>  	}
> +	maybe_null_fail_dsp__destroy(fail_dsp);

Flipping the maybe_null_fail_dsp arm from expected-failure to
expected-success removes the only check that .dispatch's second argument
is nullable.

The program under test is:

    void BPF_STRUCT_OPS(maybe_null_fail_dispatch, s32 cpu,
                        struct task_struct *p)
    {
            vtime_test = p->scx.dsq_vtime;
    }

That is a single scalar BPF_READ, which now loads regardless of whether
p carries PTR_MAYBE_NULL. In other words, it loads identically if
kernel/sched/ext/ext.c ever loses the prev__nullable suffix on
sched_ext_ops__dispatch(). The sibling positive test
(maybe_null.bpf.c maybe_null_success_dispatch, which does if (p != NULL))
also loads either way, so the .dispatch nullability marking becomes
entirely untested.

Only the .yield arm still discriminates, and it does so incidentally,
because bpf_printk("... %s ...", to->comm, ...) performs pointer
arithmetic on to (rejected), not because of a null-check requirement.

Consequently the test's own description is now inaccurate:

    struct scx_test maybe_null = {
            .name = "maybe_null",
            .description = "Verify if PTR_MAYBE_NULL works for .dispatch",

Making maybe_null_fail_dsp.bpf.c do something that is still prohibited
through a trusted-or-null pointer (pointer arithmetic on p, or a store
such as p->scx.dsq_vtime = 0) would keep the arm a real negative test.
The fail_dsp file/struct_ops names (maybe_null_fail, .name =
"maybe_null_fail_dispatch") and the description also need updating if it
stays a success case.


---
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/33379004067

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads
  2026-08-31  9:23 ` [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads Anastasios Papagiannis
  2026-08-31 10:43   ` bot+bpf-ci
@ 2026-08-31 10:55   ` sashiko-bot
  1 sibling, 0 replies; 19+ messages in thread
From: sashiko-bot @ 2026-08-31 10:55 UTC (permalink / raw)
  To: Anastasios Papagiannis; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Tests for atomic RMW operations and BPF_LOAD_ACQ on trusted-or-null pointers are missing, despite the commit message claiming they are verified.
- [Low] Multi-line comment opening '/*' is not on its own line, violating BPF subsystem guidelines.
--

commit a8459550255e4dfc12584cd94a1820a3e538fba1
Author: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>

selftests/bpf: Cover trusted-or-null BTF pointer reads

This commit updates verifier tests for unchecked trusted-or-null BTF pointer
dereferences and adds tests covering scalar reads and chained reads. It also
adds runtime tests to verify that non-NULL reads return the field value while
NULL reads return zero.

> 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
> --- 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,15 @@ 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.
>   */

[Severity: Low]
This is not a bug, but does this follow the BPF subsystem comment guidelines?
The guidelines require multi-line comments to have the opening /* on its own
line, rather than keeping the text on the same line as the opening characters.

[ ... ]

> diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
> --- a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
> +++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
[ ... ]
> +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;
> +}

[Severity: Medium]
The commit message states that the tests verify that atomic RMW operations
and BPF_LOAD_ACQ accesses remain prohibited. Does this patch actually include
those tests? The test for pointer arithmetic is present here in
handle_tp_btf_nullable_arith(), but there do not seem to be any corresponding
tests for atomic RMW operations or BPF_LOAD_ACQ accesses in this file or
elsewhere in the patch.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=5

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
  2026-08-31 10:25   ` sashiko-bot
  2026-08-31 10:30   ` bot+bpf-ci
@ 2026-09-01  3:19   ` sun jian
  2026-09-01  8:56   ` sun jian
  3 siblings, 0 replies; 19+ messages in thread
From: sun jian @ 2026-09-01  3:19 UTC (permalink / raw)
  To: Anastasios Papagiannis
  Cc: bpf, linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm,
	david, ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77

On Mon, Aug 31, 2026 at 5:52 PM Anastasios Papagiannis
<tasos.papagiannnis@gmail.com> wrote:
>
> 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 eBPF 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>
> ---
>  fs/exec.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/exec.c b/fs/exec.c
> index a14f28b15607..4b4d8f0627a7 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1453,9 +1453,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
>
>
Reviewed-by: Sun Jian <sun.jian.kdev@gmail.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null
  2026-08-31  9:23 ` [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
  2026-08-31 10:43   ` bot+bpf-ci
@ 2026-09-01  3:50   ` sun jian
  1 sibling, 0 replies; 19+ messages in thread
From: sun jian @ 2026-09-01  3:50 UTC (permalink / raw)
  To: Anastasios Papagiannis
  Cc: bpf, linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm,
	david, ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77

On Mon, Aug 31, 2026 at 5:28 PM Anastasios Papagiannis
<tasos.papagiannnis@gmail.com> wrote:
>
> Mark linux_binprm->mm as a trusted-or-null nested pointer so BPF programs
> can pass it to kfuncs after a NULL check. The linux_binprm owns this mm
> throughout the bprm_check_security hook and clears the pointer only after
> exec_mmap() installs the new address space.
>
> Signed-off-by: Anastasios Papagiannis <tasos.papagiannnis@gmail.com>
> ---
>  kernel/bpf/verifier.c | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 386401fe051d..584c79326b9c 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -5958,6 +5958,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;
>  };
> @@ -6012,6 +6016,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,
> --
> 2.55.0
>
>
Reviewed-by: Sun Jian <sun.jian.kdev@gmail.com>

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference
  2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
                     ` (2 preceding siblings ...)
  2026-09-01  3:19   ` sun jian
@ 2026-09-01  8:56   ` sun jian
  3 siblings, 0 replies; 19+ messages in thread
From: sun jian @ 2026-09-01  8:56 UTC (permalink / raw)
  To: Anastasios Papagiannis
  Cc: bpf, linux-kernel, linux-mm, linux-fsdevel, viro, brauner, akpm,
	david, ast, daniel, andrii, eddyz87, memxor, kpsingh, matt, song,
	utilityemal77

On Mon, Aug 31, 2026 at 5:52 PM Anastasios Papagiannis
<tasos.papagiannnis@gmail.com> wrote:
>
> 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 eBPF 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>
> ---
>  fs/exec.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/exec.c b/fs/exec.c
> index a14f28b15607..4b4d8f0627a7 100644
> --- a/fs/exec.c
> +++ b/fs/exec.c
> @@ -1453,9 +1453,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
>
>

An automated review by Sashiko [1] reported a potential UAF involving
bprm->file. The report identifies a real but narrower lifetime issue.

The specific PF_KTHREAD/delayed-fput scenario is not reachable through
the current in-tree path: kernel_execve() rejects PF_KTHREAD before
alloc_bprm(), and usermode helpers use user_mode_thread(). In
addition, bpf_get_file_xattr() is not available to TRACING/fentry
programs.

However, free_bprm() still drops the reference held by bprm->file
before calling bprm_drop_loader(), which is instrumentable.
linux_binprm->file is marked BTF_TYPE_SAFE_TRUSTED, so a tracing
program attached to bprm_drop_loader() can pass it to the common
bpf_dynptr_from_file() kfunc. Once the reference has been dropped,
bprm->file is an unowned raw pointer; the dynptr retains that pointer
without acquiring a file reference, and later reads dereference
file->f_mapping.

This is independent of the bprm->mm change in this patch. I plan to
send a separate fs/exec patch against the current bpf-next tree for
this issue.

[1] https://sashiko.dev/#/patchset/20260831092305.42062-1-tasos.papagiannnis@gmail.com?part=3

^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2026-09-01  8:56 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31  9:22 [PATCH bpf-next v3 0/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-08-31  9:22 ` [PATCH bpf-next v3 1/7] mm: Add copy_remote_mm_str() Anastasios Papagiannis
2026-08-31  9:23 ` [PATCH bpf-next v3 2/7] bpf: Add user memory access kfuncs for mm_struct Anastasios Papagiannis
2026-08-31 10:01   ` sashiko-bot
2026-08-31 10:43   ` bot+bpf-ci
2026-08-31  9:23 ` [PATCH bpf-next v3 3/7] exec: Clear bprm->mm before dropping its reference Anastasios Papagiannis
2026-08-31 10:25   ` sashiko-bot
2026-08-31 10:30   ` bot+bpf-ci
2026-09-01  3:19   ` sun jian
2026-09-01  8:56   ` sun jian
2026-08-31  9:23 ` [PATCH bpf-next v3 4/7] bpf: Allow reads through trusted-or-null BTF pointers Anastasios Papagiannis
2026-08-31 10:43   ` bot+bpf-ci
2026-08-31  9:23 ` [PATCH bpf-next v3 5/7] selftests/bpf: Cover trusted-or-null BTF pointer reads Anastasios Papagiannis
2026-08-31 10:43   ` bot+bpf-ci
2026-08-31 10:55   ` sashiko-bot
2026-08-31  9:23 ` [PATCH bpf-next v3 6/7] bpf: Mark linux_binprm->mm as trusted-or-null Anastasios Papagiannis
2026-08-31 10:43   ` bot+bpf-ci
2026-09-01  3:50   ` sun jian
2026-08-31  9:23 ` [PATCH bpf-next v3 7/7] selftests/bpf: Test mm_struct user memory kfuncs with linux_binprm Anastasios Papagiannis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox