--- /dev/null
+From 4fddde2a732de60bb97e3307d4eb69ac5f1d2b74 Mon Sep 17 00:00:00 2001
+From: Alexei Starovoitov <ast@kernel.org>
+Date: Mon, 13 Apr 2026 12:42:45 -0700
+Subject: bpf: Fix use-after-free in arena_vm_close on fork
+
+From: Alexei Starovoitov <ast@kernel.org>
+
+commit 4fddde2a732de60bb97e3307d4eb69ac5f1d2b74 upstream.
+
+arena_vm_open() only bumps vml->mmap_count but never registers the
+child VMA in arena->vma_list. The vml->vma always points at the
+parent VMA, so after parent munmap the pointer dangles. If the child
+then calls bpf_arena_free_pages(), zap_pages() reads the stale
+vml->vma triggering use-after-free.
+
+Fix this by preventing the arena VMA from being inherited across
+fork with VM_DONTCOPY, and preventing VMA splits via the may_split
+callback.
+
+Also reject mremap with a .mremap callback returning -EINVAL. A
+same-size mremap(MREMAP_FIXED) on the full arena VMA reaches
+copy_vma() through the following path:
+
+ check_prep_vma() - returns 0 early: new_len == old_len
+ skips VM_DONTEXPAND check
+ prep_move_vma() - vm_start == old_addr and
+ vm_end == old_addr + old_len
+ so may_split is never called
+ move_vma()
+ copy_vma_and_data()
+ copy_vma()
+ vm_area_dup() - copies vm_private_data (vml pointer)
+ vm_ops->open() - bumps vml->mmap_count
+ vm_ops->mremap() - returns -EINVAL, rollback unmaps new VMA
+
+The refcount ensures the rollback's arena_vm_close does not free
+the vml shared with the original VMA.
+
+Reported-by: Weiming Shi <bestswngs@gmail.com>
+Reported-by: Xiang Mei <xmei5@asu.edu>
+Fixes: 317460317a02 ("bpf: Introduce bpf_arena.")
+Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
+Link: https://lore.kernel.org/r/20260413194245.21449-1-alexei.starovoitov@gmail.com
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/bpf/arena.c | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+--- a/kernel/bpf/arena.c
++++ b/kernel/bpf/arena.c
+@@ -236,6 +236,16 @@ static void arena_vm_open(struct vm_area
+ refcount_inc(&vml->mmap_count);
+ }
+
++static int arena_vm_may_split(struct vm_area_struct *vma, unsigned long addr)
++{
++ return -EINVAL;
++}
++
++static int arena_vm_mremap(struct vm_area_struct *vma)
++{
++ return -EINVAL;
++}
++
+ static void arena_vm_close(struct vm_area_struct *vma)
+ {
+ struct bpf_map *map = vma->vm_file->private_data;
+@@ -299,6 +309,8 @@ out:
+
+ static const struct vm_operations_struct arena_vm_ops = {
+ .open = arena_vm_open,
++ .may_split = arena_vm_may_split,
++ .mremap = arena_vm_mremap,
+ .close = arena_vm_close,
+ .fault = arena_vm_fault,
+ };
+@@ -368,10 +380,11 @@ static int arena_map_mmap(struct bpf_map
+ arena->user_vm_end = vma->vm_end;
+ /*
+ * bpf_map_mmap() checks that it's being mmaped as VM_SHARED and
+- * clears VM_MAYEXEC. Set VM_DONTEXPAND as well to avoid
+- * potential change of user_vm_start.
++ * clears VM_MAYEXEC. Set VM_DONTEXPAND to avoid potential change
++ * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from
++ * being copied into the child process on fork.
+ */
+- vm_flags_set(vma, VM_DONTEXPAND);
++ vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);
+ vma->vm_ops = &arena_vm_ops;
+ return 0;
+ }