mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
bpf: Fix use-after-free in arena_vm_close on fork
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: 317460317a ("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>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user