* [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma()
@ 2026-06-30 3:25 Sanghyun Park
2026-06-30 3:37 ` sashiko-bot
2026-07-01 19:48 ` Andrii Nakryiko
0 siblings, 2 replies; 3+ messages in thread
From: Sanghyun Park @ 2026-06-30 3:25 UTC (permalink / raw)
To: Yonghong Song, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko
Cc: Sanghyun Park, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Kumar Kartikeya Dwivedi, John Fastabend, KP Singh,
Stanislav Fomichev, Hao Luo, Jiri Olsa, Emil Tsalapatis,
Puranjay Mohan, bpf, linux-kernel
bpf_find_vma() reads task->mm and calls mmap_read_trylock(mm) without
holding a reference on the mm. On a foreign task, a concurrent exit_mm()
can free the mm_struct between the lockless read and the trylock,
resulting in a use-after-free. mm_struct is not SLAB_TYPESAFE_BY_RCU.
For the current task, task->mm is stable. For a foreign task, pin the mm
under task->alloc_lock and release it with mmput_async(), mirroring commit
d8e27d2d22b6 ("bpf: fix mm lifecycle in open-coded task_vma iterator").
Use spin_trylock() instead of get_task_mm() so BPF context does not block
on alloc_lock. Reject irqs-disabled contexts and !CONFIG_MMU on the
foreign-task path because dropping the mm reference is not safe there.
Race:
CPU0 (BPF program) CPU1 (exiting task)
============================ ==========================
bpf_find_vma(foreign_task):
mm = task->mm
exit_mm():
task->mm = NULL
mmput(mm) -> frees mm_struct
mmap_read_trylock(mm)
// UAF on mm
Fixes: 7c7e3d31e785 ("bpf: Introduce helper bpf_find_vma")
Acked-by: Yonghong Song <yonghong.song@linux.dev>
Signed-off-by: Sanghyun Park <sanghyun.park.cnu@gmail.com>
---
v6:
- Restore bpf_iter_mmput_async() because bpf_iter_task_vma_destroy()
still uses it.
- Keep the direct mmput_async() call in bpf_find_vma(), but guard it with
CONFIG_MMU because mmput_async() is declared only for CONFIG_MMU builds.
v5: https://lore.kernel.org/bpf/20260630023443.3026627-2-sanghyun.park.cnu@gmail.com/
- Replace bpf_iter_mmput_async() with mmput_async() directly as suggested
by Yonghong.
- Add Yonghong's Acked-by.
v4: https://lore.kernel.org/bpf/20260610024637.343364-1-sanghyun.park.cnu@gmail.com/
- Use [PATCH bpf-next] subject as requested by Alexei.
- Add the missing BPF maintainers/reviewers to Cc.
v3: https://lore.kernel.org/bpf/20260609105216.3536839-1-sanghyun.park.cnu@gmail.com/
- Drop get_task_mm()+mmput(); mirror d8e27d2d22b6 with alloc_lock
trylock + mmput_async(). (Yonghong Song)
- Reject irqs-disabled contexts on the foreign-task path.
- Reject foreign-task path when !CONFIG_MMU: bpf_iter_mmput_async()
falls back to mmput() which may sleep, and bpf_find_vma() can run
in non-sleepable context.
- Shorten the foreign-task rationale comment and trim the changelog body.
- Fix the v2's whitespace damage.
v2: https://lore.kernel.org/bpf/CAOrxSK5_7e4114VyfEU9htGi+UneuNt88fGVKOAa3_ZenPOFkA@mail.gmail.com/
kernel/bpf/task_iter.c | 36 +++++++++++++++++++++++++++++++++---
1 file changed, 33 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/task_iter.c b/kernel/bpf/task_iter.c
index fc5f463ca529a..1d56193caf5e4 100644
--- a/kernel/bpf/task_iter.c
+++ b/kernel/bpf/task_iter.c
@@ -760,6 +760,7 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
struct mmap_unlock_irq_work *work = NULL;
struct vm_area_struct *vma;
bool irq_work_busy = false;
+ bool mmput_needed = false;
struct mm_struct *mm;
int ret = -ENOENT;
@@ -769,14 +770,38 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
if (!task)
return -ENOENT;
- mm = task->mm;
+ if (task == current) {
+ mm = task->mm;
+ } else {
+ /*
+ * Foreign task: pin task->mm against a concurrent exit_mm().
+ * Use trylock on alloc_lock instead of get_task_mm()'s
+ * blocking task_lock() to avoid deadlocking the target task.
+ */
+ if (!IS_ENABLED(CONFIG_MMU))
+ return -EOPNOTSUPP;
+ if (irqs_disabled())
+ return -EBUSY;
+ if (!spin_trylock(&task->alloc_lock))
+ return -EBUSY;
+ mm = task->mm;
+ if (mm && !(task->flags & PF_KTHREAD)) {
+ mmget(mm);
+ mmput_needed = true;
+ } else {
+ mm = NULL;
+ }
+ spin_unlock(&task->alloc_lock);
+ }
if (!mm)
return -ENOENT;
irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
- if (irq_work_busy || !mmap_read_trylock(mm))
- return -EBUSY;
+ if (irq_work_busy || !mmap_read_trylock(mm)) {
+ ret = -EBUSY;
+ goto out;
+ }
vma = find_vma(mm, start);
@@ -786,6 +811,11 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
ret = 0;
}
bpf_mmap_unlock_mm(work, mm);
+out:
+#ifdef CONFIG_MMU
+ if (mmput_needed)
+ mmput_async(mm);
+#endif
return ret;
}
--
2.48.1
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma()
2026-06-30 3:25 [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma() Sanghyun Park
@ 2026-06-30 3:37 ` sashiko-bot
2026-07-01 19:48 ` Andrii Nakryiko
1 sibling, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-06-30 3:37 UTC (permalink / raw)
To: Sanghyun Park; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Pre-existing race condition between concurrent NMIs and HW IRQs leading to a leaked mmap_read_trylock() in bpf_find_vma().
--
commit 4672ecd9d6bd9a98e0de397e206fa5c87190b657
Author: Sanghyun Park <sanghyun.park.cnu@gmail.com>
bpf: Fix use-after-free on mm_struct in bpf_find_vma()
This commit fixes a use-after-free on mm_struct in bpf_find_vma() by pinning
the mm_struct via task->alloc_lock for foreign tasks. It introduces a trylock
and mmput_async() path to safely handle the mm lifecycle when interacting
with tasks other than current.
> diff --git a/kernel/bpf/task_iter.c b/kernel/bpf/task_iter.c
> index e791ae065c39b..22db3fcedd2fb 100644
> --- a/kernel/bpf/task_iter.c
> +++ b/kernel/bpf/task_iter.c
[ ... ]
> @@ -765,14 +766,38 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
[ ... ]
> if (!mm)
> return -ENOENT;
>
> irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
>
> - if (irq_work_busy || !mmap_read_trylock(mm))
> - return -EBUSY;
> + if (irq_work_busy || !mmap_read_trylock(mm)) {
> + ret = -EBUSY;
> + goto out;
> + }
>
> vma = find_vma(mm, start);
>
> @@ -782,6 +807,11 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
> ret = 0;
> }
> bpf_mmap_unlock_mm(work, mm);
[Severity: High]
This isn't a bug introduced by this patch, but could a race condition between
concurrent NMIs and hardware IRQs leak the mmap read lock here?
If a hardware IRQ calls bpf_find_vma(), it fetches the per-CPU
mmap_unlock_irq_work structure via bpf_mmap_unlock_get_irq_work(). If an NMI
interrupts execution before bpf_mmap_unlock_mm() is called and queues the
work, an NMI tracing program could also call bpf_find_vma().
Because irqs_disabled() is true in the NMI, it fetches the exact same per-CPU
work structure. The NMI would successfully acquire mmap_read_trylock(), set
work->mm to its mm, queue the work, and return.
When the hardware IRQ resumes, it takes its own mmap_read_trylock(), calls
bpf_mmap_unlock_mm(), overwrites work->mm with the hardware IRQ's mm, and
attempts to queue the already-pending work. When the irq_work later executes,
it only drops the lock for the hardware IRQ's mm, permanently leaking the
read lock for the NMI's mm.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260630032543.3165720-1-sanghyun.park.cnu@gmail.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma()
2026-06-30 3:25 [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma() Sanghyun Park
2026-06-30 3:37 ` sashiko-bot
@ 2026-07-01 19:48 ` Andrii Nakryiko
1 sibling, 0 replies; 3+ messages in thread
From: Andrii Nakryiko @ 2026-07-01 19:48 UTC (permalink / raw)
To: Sanghyun Park, Puranjay Mohan
Cc: Yonghong Song, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Song Liu,
Kumar Kartikeya Dwivedi, John Fastabend, KP Singh,
Stanislav Fomichev, Hao Luo, Jiri Olsa, Emil Tsalapatis, bpf,
linux-kernel
On Mon, Jun 29, 2026 at 8:25 PM Sanghyun Park
<sanghyun.park.cnu@gmail.com> wrote:
>
> bpf_find_vma() reads task->mm and calls mmap_read_trylock(mm) without
> holding a reference on the mm. On a foreign task, a concurrent exit_mm()
> can free the mm_struct between the lockless read and the trylock,
> resulting in a use-after-free. mm_struct is not SLAB_TYPESAFE_BY_RCU.
>
> For the current task, task->mm is stable. For a foreign task, pin the mm
> under task->alloc_lock and release it with mmput_async(), mirroring commit
> d8e27d2d22b6 ("bpf: fix mm lifecycle in open-coded task_vma iterator").
> Use spin_trylock() instead of get_task_mm() so BPF context does not block
> on alloc_lock. Reject irqs-disabled contexts and !CONFIG_MMU on the
> foreign-task path because dropping the mm reference is not safe there.
>
> Race:
>
> CPU0 (BPF program) CPU1 (exiting task)
> ============================ ==========================
> bpf_find_vma(foreign_task):
> mm = task->mm
> exit_mm():
> task->mm = NULL
> mmput(mm) -> frees mm_struct
> mmap_read_trylock(mm)
> // UAF on mm
>
> Fixes: 7c7e3d31e785 ("bpf: Introduce helper bpf_find_vma")
> Acked-by: Yonghong Song <yonghong.song@linux.dev>
> Signed-off-by: Sanghyun Park <sanghyun.park.cnu@gmail.com>
> ---
Puranjay, PTAL, I think you've been fixing similar issue in
bpf_iter_task_vma_new(), would be nice for you to check this fix as
well. Thanks!
> v6:
> - Restore bpf_iter_mmput_async() because bpf_iter_task_vma_destroy()
> still uses it.
> - Keep the direct mmput_async() call in bpf_find_vma(), but guard it with
> CONFIG_MMU because mmput_async() is declared only for CONFIG_MMU builds.
> v5: https://lore.kernel.org/bpf/20260630023443.3026627-2-sanghyun.park.cnu@gmail.com/
> - Replace bpf_iter_mmput_async() with mmput_async() directly as suggested
> by Yonghong.
> - Add Yonghong's Acked-by.
> v4: https://lore.kernel.org/bpf/20260610024637.343364-1-sanghyun.park.cnu@gmail.com/
> - Use [PATCH bpf-next] subject as requested by Alexei.
> - Add the missing BPF maintainers/reviewers to Cc.
> v3: https://lore.kernel.org/bpf/20260609105216.3536839-1-sanghyun.park.cnu@gmail.com/
> - Drop get_task_mm()+mmput(); mirror d8e27d2d22b6 with alloc_lock
> trylock + mmput_async(). (Yonghong Song)
> - Reject irqs-disabled contexts on the foreign-task path.
> - Reject foreign-task path when !CONFIG_MMU: bpf_iter_mmput_async()
> falls back to mmput() which may sleep, and bpf_find_vma() can run
> in non-sleepable context.
> - Shorten the foreign-task rationale comment and trim the changelog body.
> - Fix the v2's whitespace damage.
> v2: https://lore.kernel.org/bpf/CAOrxSK5_7e4114VyfEU9htGi+UneuNt88fGVKOAa3_ZenPOFkA@mail.gmail.com/
>
> kernel/bpf/task_iter.c | 36 +++++++++++++++++++++++++++++++++---
> 1 file changed, 33 insertions(+), 3 deletions(-)
>
> diff --git a/kernel/bpf/task_iter.c b/kernel/bpf/task_iter.c
> index fc5f463ca529a..1d56193caf5e4 100644
> --- a/kernel/bpf/task_iter.c
> +++ b/kernel/bpf/task_iter.c
> @@ -760,6 +760,7 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
> struct mmap_unlock_irq_work *work = NULL;
> struct vm_area_struct *vma;
> bool irq_work_busy = false;
> + bool mmput_needed = false;
> struct mm_struct *mm;
> int ret = -ENOENT;
>
> @@ -769,14 +770,38 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
> if (!task)
> return -ENOENT;
>
> - mm = task->mm;
> + if (task == current) {
> + mm = task->mm;
> + } else {
> + /*
> + * Foreign task: pin task->mm against a concurrent exit_mm().
> + * Use trylock on alloc_lock instead of get_task_mm()'s
> + * blocking task_lock() to avoid deadlocking the target task.
> + */
> + if (!IS_ENABLED(CONFIG_MMU))
> + return -EOPNOTSUPP;
> + if (irqs_disabled())
> + return -EBUSY;
> + if (!spin_trylock(&task->alloc_lock))
> + return -EBUSY;
> + mm = task->mm;
> + if (mm && !(task->flags & PF_KTHREAD)) {
> + mmget(mm);
> + mmput_needed = true;
> + } else {
> + mm = NULL;
> + }
> + spin_unlock(&task->alloc_lock);
> + }
> if (!mm)
> return -ENOENT;
>
> irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
>
> - if (irq_work_busy || !mmap_read_trylock(mm))
> - return -EBUSY;
> + if (irq_work_busy || !mmap_read_trylock(mm)) {
> + ret = -EBUSY;
> + goto out;
> + }
>
> vma = find_vma(mm, start);
>
> @@ -786,6 +811,11 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
> ret = 0;
> }
> bpf_mmap_unlock_mm(work, mm);
> +out:
> +#ifdef CONFIG_MMU
> + if (mmput_needed)
> + mmput_async(mm);
> +#endif
> return ret;
> }
>
> --
> 2.48.1
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-01 19:48 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30 3:25 [PATCH bpf-next v6] bpf: Fix use-after-free on mm_struct in bpf_find_vma() Sanghyun Park
2026-06-30 3:37 ` sashiko-bot
2026-07-01 19:48 ` Andrii Nakryiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox