]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bpf: Fix use-after-free in arena_vm_close on fork
authorAlexei Starovoitov <ast@kernel.org>
Mon, 13 Apr 2026 19:42:45 +0000 (12:42 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 15 Apr 2026 19:08:55 +0000 (12:08 -0700)
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>
kernel/bpf/arena.c

index f355cf1c1a1693c5ace9e444069c421506b0cc16..9c68c9b0b24adf76090df1953247463f71d0200d 100644 (file)
@@ -341,6 +341,16 @@ static void arena_vm_open(struct vm_area_struct *vma)
        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;
@@ -417,6 +427,8 @@ out_unlock_sigsegv:
 
 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,
 };
@@ -486,10 +498,11 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
        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;
 }