public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf v2 0/2] bpf: Fix arena VMA use-after-free on fork
@ 2026-04-11 11:29 Weiming Shi
  2026-04-11 11:29 ` [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA " Weiming Shi
  2026-04-11 11:29 ` [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free " Weiming Shi
  0 siblings, 2 replies; 5+ messages in thread
From: Weiming Shi @ 2026-04-11 11:29 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Barret Rhoden, bpf, linux-kernel, Xiang Mei, Weiming Shi

arena_vm_open() only increments a refcount on the shared vma_list entry
but never registers the new VMA. After fork + parent munmap, vml->vma
becomes a dangling pointer. bpf_arena_free_pages -> zap_pages then
dereferences it, causing a slab-use-after-free in zap_page_range_single.

Patch 1 fixes the bug by giving each VMA its own vma_list entry,
following the HugeTLB vma_lock pattern (hugetlb_vm_op_open).
Patch 2 adds a selftest that reproduces the issue.

Changes since v1:
- Added missing Reported-by tag

Weiming Shi (2):
  bpf: Fix use-after-free of arena VMA on fork
  selftests/bpf: Add test for arena VMA use-after-free on fork

 kernel/bpf/arena.c                            | 26 ++++--
 .../selftests/bpf/prog_tests/arena_fork.c     | 86 +++++++++++++++++++
 .../testing/selftests/bpf/progs/arena_fork.c  | 41 +++++++++
 3 files changed, 148 insertions(+), 5 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_fork.c
 create mode 100644 tools/testing/selftests/bpf/progs/arena_fork.c

-- 
2.43.0


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

* [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA on fork
  2026-04-11 11:29 [PATCH bpf v2 0/2] bpf: Fix arena VMA use-after-free on fork Weiming Shi
@ 2026-04-11 11:29 ` Weiming Shi
  2026-04-11 17:57   ` Emil Tsalapatis
  2026-04-11 11:29 ` [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free " Weiming Shi
  1 sibling, 1 reply; 5+ messages in thread
From: Weiming Shi @ 2026-04-11 11:29 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Barret Rhoden, bpf, linux-kernel, Xiang Mei, Weiming Shi

arena_vm_open() only increments a refcount on the shared vma_list entry
but never registers the new VMA or updates the stored vma pointer. When
the original VMA is unmapped while a forked/split copy still exists,
arena_vm_close() drops the refcount without freeing the vma_list entry.
The entry's vma pointer now refers to a freed vm_area_struct. A
subsequent bpf_arena_free_pages() call iterates vma_list and passes
the dangling pointer to zap_page_range_single(), causing a
use-after-free.

The bug is reachable by any process with CAP_BPF and CAP_PERFMON that
can create a BPF_MAP_TYPE_ARENA, mmap it, and fork. It triggers
deterministically -- no race condition is involved.

 BUG: KASAN: slab-use-after-free in zap_page_range_single (mm/memory.c:2234)
 Call Trace:
  <TASK>
  zap_page_range_single+0x101/0x110   mm/memory.c:2234
  zap_pages+0x80/0xf0                 kernel/bpf/arena.c:658
  arena_free_pages+0x67a/0x860        kernel/bpf/arena.c:712
  bpf_prog_test_run_syscall+0x3da     net/bpf/test_run.c:1640
  __sys_bpf+0x1662/0x50b0             kernel/bpf/syscall.c:6267
  __x64_sys_bpf+0x73/0xb0             kernel/bpf/syscall.c:6360
  do_syscall_64+0xf1/0x530            arch/x86/entry/syscall_64.c:63
  entry_SYSCALL_64_after_hwframe+0x77  arch/x86/entry/entry_64.S:130
  </TASK>

Fix this by giving each VMA its own vma_list entry, following the
HugeTLB vma_lock pattern (hugetlb_vm_op_open). arena_vm_open() now
detects an inherited vm_private_data pointer via the vma_lock->vma !=
vma check, clears it, and allocates a fresh entry for the new VMA.
arena_vm_close() unconditionally removes and frees the entry. The
shared refcount is no longer needed and is removed.

Fixes: b90d77e5fd78 ("bpf: Fix remap of arena.")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
Changes since v1:
- Added missing Reported-by tag

 kernel/bpf/arena.c | 26 +++++++++++++++++++++-----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index f355cf1c1a16..3a156ec473a8 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -317,7 +317,6 @@ static u64 arena_map_mem_usage(const struct bpf_map *map)
 struct vma_list {
 	struct vm_area_struct *vma;
 	struct list_head head;
-	refcount_t mmap_count;
 };
 
 static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
@@ -327,7 +326,6 @@ static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
 	vml = kmalloc_obj(*vml);
 	if (!vml)
 		return -ENOMEM;
-	refcount_set(&vml->mmap_count, 1);
 	vma->vm_private_data = vml;
 	vml->vma = vma;
 	list_add(&vml->head, &arena->vma_list);
@@ -336,9 +334,28 @@ static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
 
 static void arena_vm_open(struct vm_area_struct *vma)
 {
+	struct bpf_map *map = vma->vm_file->private_data;
+	struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
 	struct vma_list *vml = vma->vm_private_data;
 
-	refcount_inc(&vml->mmap_count);
+	/*
+	 * If vm_private_data points to a vma_list for a different VMA, it was
+	 * inherited via vm_area_dup (fork or split). Clear it and allocate a
+	 * fresh entry for this VMA, following the HugeTLB vma_lock pattern.
+	 */
+	if (vml && vml->vma != vma)
+		vma->vm_private_data = NULL;
+
+	if (vma->vm_private_data)
+		return;
+
+	vml = kmalloc_obj(*vml);
+	if (!vml)
+		return;
+	vml->vma = vma;
+	vma->vm_private_data = vml;
+	guard(mutex)(&arena->lock);
+	list_add(&vml->head, &arena->vma_list);
 }
 
 static void arena_vm_close(struct vm_area_struct *vma)
@@ -347,10 +364,9 @@ static void arena_vm_close(struct vm_area_struct *vma)
 	struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
 	struct vma_list *vml = vma->vm_private_data;
 
-	if (!refcount_dec_and_test(&vml->mmap_count))
+	if (!vml)
 		return;
 	guard(mutex)(&arena->lock);
-	/* update link list under lock */
 	list_del(&vml->head);
 	vma->vm_private_data = NULL;
 	kfree(vml);
-- 
2.43.0


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

* [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free on fork
  2026-04-11 11:29 [PATCH bpf v2 0/2] bpf: Fix arena VMA use-after-free on fork Weiming Shi
  2026-04-11 11:29 ` [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA " Weiming Shi
@ 2026-04-11 11:29 ` Weiming Shi
  2026-04-11 17:33   ` Emil Tsalapatis
  1 sibling, 1 reply; 5+ messages in thread
From: Weiming Shi @ 2026-04-11 11:29 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Barret Rhoden, bpf, linux-kernel, Xiang Mei, Weiming Shi

Add a selftest that reproduces the arena VMA use-after-free fixed in
the previous commit. The test creates an arena, mmaps it, allocates
pages via BPF, forks, has the parent munmap the arena, then has the
child call bpf_arena_free_pages. Without the fix this triggers a
KASAN slab-use-after-free in zap_page_range_single.

Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
 .../selftests/bpf/prog_tests/arena_fork.c     | 86 +++++++++++++++++++
 .../testing/selftests/bpf/progs/arena_fork.c  | 41 +++++++++
 2 files changed, 127 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_fork.c
 create mode 100644 tools/testing/selftests/bpf/progs/arena_fork.c

diff --git a/tools/testing/selftests/bpf/prog_tests/arena_fork.c b/tools/testing/selftests/bpf/prog_tests/arena_fork.c
new file mode 100644
index 000000000000..445574827891
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_fork.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 */
+
+/*
+ * Test that forking a process with an arena mmap does not cause a
+ * use-after-free when the parent unmaps and the child frees arena pages.
+ *
+ * The bug: arena_vm_open() only incremented a refcount but never registered
+ * the child's VMA. After parent munmap, vml->vma pointed to a freed
+ * vm_area_struct. bpf_arena_free_pages -> zap_pages would then UAF.
+ */
+#include <test_progs.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/user.h>
+#ifndef PAGE_SIZE
+#define PAGE_SIZE getpagesize()
+#endif
+
+#include "arena_fork.skel.h"
+
+void test_arena_fork(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct bpf_map_info info = {};
+	__u32 info_len = sizeof(info);
+	struct arena_fork *skel;
+	size_t arena_sz;
+	void *arena_addr;
+	int arena_fd, ret, status;
+	pid_t pid;
+
+	skel = arena_fork__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open_and_load"))
+		return;
+
+	arena_fd = bpf_map__fd(skel->maps.arena);
+
+	/* libbpf mmaps the arena via initial_value */
+	arena_addr = bpf_map__initial_value(skel->maps.arena, &arena_sz);
+	if (!ASSERT_OK_PTR(arena_addr, "arena_mmap"))
+		goto out;
+
+	/* Get real arena byte size for munmap */
+	bpf_map_get_info_by_fd(arena_fd, &info, &info_len);
+	arena_sz = (size_t)info.max_entries * PAGE_SIZE;
+
+	/* Allocate 4 pages in the arena via BPF */
+	ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.arena_alloc),
+				     &opts);
+	if (!ASSERT_OK(ret, "alloc_run") ||
+	    !ASSERT_OK(opts.retval, "alloc_ret"))
+		goto out;
+
+	/* Fault in a page so zap_pages has work to do */
+	((char *)arena_addr)[0] = 'A';
+
+	/* Fork: child inherits the arena VMA */
+	pid = fork();
+	if (!ASSERT_GE(pid, 0, "fork"))
+		goto out;
+
+	if (pid == 0) {
+		/* Child: parent will unmap first, then we free pages.
+		 * Without the fix, this triggers UAF in zap_pages.
+		 */
+		LIBBPF_OPTS(bpf_test_run_opts, child_opts);
+		int free_fd = bpf_program__fd(skel->progs.arena_free);
+
+		usleep(200000); /* let parent munmap first */
+
+		ret = bpf_prog_test_run_opts(free_fd, &child_opts);
+		_exit(ret || child_opts.retval);
+	}
+
+	/* Parent: unmap the arena, making vml->vma stale */
+	munmap(arena_addr, arena_sz);
+
+	/* Wait for child -- if kernel UAFs, child will crash/hang */
+	waitpid(pid, &status, 0);
+	ASSERT_TRUE(WIFEXITED(status), "child_exited");
+	ASSERT_EQ(WEXITSTATUS(status), 0, "child_exit_code");
+out:
+	arena_fork__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_fork.c b/tools/testing/selftests/bpf/progs/arena_fork.c
new file mode 100644
index 000000000000..b1f8435f1834
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_fork.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_arena_common.h"
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 16); /* number of pages */
+#ifdef __TARGET_ARCH_arm64
+	__ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
+#else
+	__ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
+#endif
+} arena SEC(".maps");
+
+void __arena *alloc_addr;
+
+SEC("syscall")
+int arena_alloc(void *ctx)
+{
+	void __arena *p;
+
+	p = bpf_arena_alloc_pages(&arena, NULL, 4, NUMA_NO_NODE, 0);
+	if (!p)
+		return 1;
+	alloc_addr = p;
+	return 0;
+}
+
+SEC("syscall")
+int arena_free(void *ctx)
+{
+	if (!alloc_addr)
+		return 1;
+	bpf_arena_free_pages(&arena, alloc_addr, 4);
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.43.0


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

* Re: [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free on fork
  2026-04-11 11:29 ` [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free " Weiming Shi
@ 2026-04-11 17:33   ` Emil Tsalapatis
  0 siblings, 0 replies; 5+ messages in thread
From: Emil Tsalapatis @ 2026-04-11 17:33 UTC (permalink / raw)
  To: Weiming Shi, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Barret Rhoden, bpf, linux-kernel, Xiang Mei

On Sat Apr 11, 2026 at 7:29 AM EDT, Weiming Shi wrote:
> Add a selftest that reproduces the arena VMA use-after-free fixed in
> the previous commit. The test creates an arena, mmaps it, allocates
> pages via BPF, forks, has the parent munmap the arena, then has the
> child call bpf_arena_free_pages. Without the fix this triggers a
> KASAN slab-use-after-free in zap_page_range_single.
>
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> ---
>  .../selftests/bpf/prog_tests/arena_fork.c     | 86 +++++++++++++++++++
>  .../testing/selftests/bpf/progs/arena_fork.c  | 41 +++++++++
>  2 files changed, 127 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_fork.c
>  create mode 100644 tools/testing/selftests/bpf/progs/arena_fork.c
>

The test doesn't work for me as advertised. Does it fail for you under
vmtest without patch 1/2?

The test doesn't fail on base vmtest for me, even without the previous patch,
because KASAN isn't turned on for the CI. With KASAN the test triggers
the splat just fine. 

Should we maybe turn on KASAN and panic_on_warn by default on vmtest?

> diff --git a/tools/testing/selftests/bpf/prog_tests/arena_fork.c b/tools/testing/selftests/bpf/prog_tests/arena_fork.c
> new file mode 100644
> index 000000000000..445574827891
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/arena_fork.c
> @@ -0,0 +1,86 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 */
> +

No actual copyright holder.

> +/*
> + * Test that forking a process with an arena mmap does not cause a
> + * use-after-free when the parent unmaps and the child frees arena pages.
> + *
> + * The bug: arena_vm_open() only incremented a refcount but never registered
> + * the child's VMA. After parent munmap, vml->vma pointed to a freed
> + * vm_area_struct. bpf_arena_free_pages -> zap_pages would then UAF.

Remove this, there is no point keeping information about past bugs in
the tests. The first sentence is enough.

> + */
> +#include <test_progs.h>
> +#include <sys/mman.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <sys/user.h>
> +#ifndef PAGE_SIZE
> +#define PAGE_SIZE getpagesize()
> +#endif

Use sysconf to get the page size.

> +
> +#include "arena_fork.skel.h"
> +
> +void test_arena_fork(void)
> +{
> +	LIBBPF_OPTS(bpf_test_run_opts, opts);
> +	struct bpf_map_info info = {};
> +	__u32 info_len = sizeof(info);
> +	struct arena_fork *skel;
> +	size_t arena_sz;
> +	void *arena_addr;
> +	int arena_fd, ret, status;
> +	pid_t pid;
> +
> +	skel = arena_fork__open_and_load();
> +	if (!ASSERT_OK_PTR(skel, "open_and_load"))
> +		return;
> +
> +	arena_fd = bpf_map__fd(skel->maps.arena);
> +
> +	/* libbpf mmaps the arena via initial_value */
> +	arena_addr = bpf_map__initial_value(skel->maps.arena, &arena_sz);
> +	if (!ASSERT_OK_PTR(arena_addr, "arena_mmap"))
> +		goto out;
> +
> +	/* Get real arena byte size for munmap */
> +	bpf_map_get_info_by_fd(arena_fd, &info, &info_len);
> +	arena_sz = (size_t)info.max_entries * PAGE_SIZE;
> +
> +	/* Allocate 4 pages in the arena via BPF */
> +	ret = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.arena_alloc),
> +				     &opts);
> +	if (!ASSERT_OK(ret, "alloc_run") ||
> +	    !ASSERT_OK(opts.retval, "alloc_ret"))
> +		goto out;
> +
> +	/* Fault in a page so zap_pages has work to do */
> +	((char *)arena_addr)[0] = 'A';
> +
> +	/* Fork: child inherits the arena VMA */
> +	pid = fork();
> +	if (!ASSERT_GE(pid, 0, "fork"))
> +		goto out;
> +
> +	if (pid == 0) {
> +		/* Child: parent will unmap first, then we free pages.
> +		 * Without the fix, this triggers UAF in zap_pages.

Again, "the fix" isn't clear. Remove the second sentence.

> +		 */
> +		LIBBPF_OPTS(bpf_test_run_opts, child_opts);
> +		int free_fd = bpf_program__fd(skel->progs.arena_free);
> +
> +		usleep(200000); /* let parent munmap first */

This is dependable on vmtest, and we do use usleep in other tests
so I'd say it's a valid use.

> +
> +		ret = bpf_prog_test_run_opts(free_fd, &child_opts);
> +		_exit(ret || child_opts.retval);
> +	}
> +
> +	/* Parent: unmap the arena, making vml->vma stale */
> +	munmap(arena_addr, arena_sz);
> +
> +	/* Wait for child -- if kernel UAFs, child will crash/hang */

That's not the case. The test exits with a success for me.

> +	waitpid(pid, &status, 0);
> +	ASSERT_TRUE(WIFEXITED(status), "child_exited");
> +	ASSERT_EQ(WEXITSTATUS(status), 0, "child_exit_code");
> +out:
> +	arena_fork__destroy(skel);
> +}
> diff --git a/tools/testing/selftests/bpf/progs/arena_fork.c b/tools/testing/selftests/bpf/progs/arena_fork.c
> new file mode 100644
> index 000000000000..b1f8435f1834
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/arena_fork.c
> @@ -0,0 +1,41 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2026 */

There's no copyright holder here, add one or remove the line it.

> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_arena_common.h"
> +
> +struct {
> +	__uint(type, BPF_MAP_TYPE_ARENA);
> +	__uint(map_flags, BPF_F_MMAPABLE);
> +	__uint(max_entries, 16); /* number of pages */
> +#ifdef __TARGET_ARCH_arm64
> +	__ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
> +#else
> +	__ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
> +#endif
> +} arena SEC(".maps");
> +
> +void __arena *alloc_addr;
> +
> +SEC("syscall")
> +int arena_alloc(void *ctx)
> +{
> +	void __arena *p;
> +
> +	p = bpf_arena_alloc_pages(&arena, NULL, 4, NUMA_NO_NODE, 0);
> +	if (!p)
> +		return 1;
> +	alloc_addr = p;
> +	return 0;
> +}
> +
> +SEC("syscall")
> +int arena_free(void *ctx)
> +{
> +	if (!alloc_addr)
> +		return 1;
> +	bpf_arena_free_pages(&arena, alloc_addr, 4);
> +	return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";


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

* Re: [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA on fork
  2026-04-11 11:29 ` [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA " Weiming Shi
@ 2026-04-11 17:57   ` Emil Tsalapatis
  0 siblings, 0 replies; 5+ messages in thread
From: Emil Tsalapatis @ 2026-04-11 17:57 UTC (permalink / raw)
  To: Weiming Shi, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko
  Cc: Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
	John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
	Barret Rhoden, bpf, linux-kernel, Xiang Mei

On Sat Apr 11, 2026 at 7:29 AM EDT, Weiming Shi wrote:
> arena_vm_open() only increments a refcount on the shared vma_list entry
> but never registers the new VMA or updates the stored vma pointer. When
> the original VMA is unmapped while a forked/split copy still exists,
> arena_vm_close() drops the refcount without freeing the vma_list entry.
> The entry's vma pointer now refers to a freed vm_area_struct. A
> subsequent bpf_arena_free_pages() call iterates vma_list and passes
> the dangling pointer to zap_page_range_single(), causing a
> use-after-free.
>
> The bug is reachable by any process with CAP_BPF and CAP_PERFMON that
> can create a BPF_MAP_TYPE_ARENA, mmap it, and fork. It triggers
> deterministically -- no race condition is involved.
>
>  BUG: KASAN: slab-use-after-free in zap_page_range_single (mm/memory.c:2234)
>  Call Trace:
>   <TASK>
>   zap_page_range_single+0x101/0x110   mm/memory.c:2234
>   zap_pages+0x80/0xf0                 kernel/bpf/arena.c:658
>   arena_free_pages+0x67a/0x860        kernel/bpf/arena.c:712
>   bpf_prog_test_run_syscall+0x3da     net/bpf/test_run.c:1640
>   __sys_bpf+0x1662/0x50b0             kernel/bpf/syscall.c:6267
>   __x64_sys_bpf+0x73/0xb0             kernel/bpf/syscall.c:6360
>   do_syscall_64+0xf1/0x530            arch/x86/entry/syscall_64.c:63
>   entry_SYSCALL_64_after_hwframe+0x77  arch/x86/entry/entry_64.S:130
>   </TASK>
>
> Fix this by giving each VMA its own vma_list entry, following the
> HugeTLB vma_lock pattern (hugetlb_vm_op_open). arena_vm_open() now

I'm not a fan of this framing. We're not "following the HugeTLB vma_lock
pattern", we're (reasonably) tracking the VMA of the child separately by
registering it when arena_vm_open is called for it during fork().

> detects an inherited vm_private_data pointer via the vma_lock->vma !=
> vma check, clears it, and allocates a fresh entry for the new VMA.
> arena_vm_close() unconditionally removes and frees the entry. The
> shared refcount is no longer needed and is removed.
>
> Fixes: b90d77e5fd78 ("bpf: Fix remap of arena.")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
> ---
> Changes since v1:
> - Added missing Reported-by tag
>
>  kernel/bpf/arena.c | 26 +++++++++++++++++++++-----
>  1 file changed, 21 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index f355cf1c1a16..3a156ec473a8 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -317,7 +317,6 @@ static u64 arena_map_mem_usage(const struct bpf_map *map)
>  struct vma_list {
>  	struct vm_area_struct *vma;
>  	struct list_head head;
> -	refcount_t mmap_count;

This is reasonable for arenas if we track each VMA separately. 

>  };
>  
>  static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
> @@ -327,7 +326,6 @@ static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
>  	vml = kmalloc_obj(*vml);
>  	if (!vml)
>  		return -ENOMEM;
> -	refcount_set(&vml->mmap_count, 1);
>  	vma->vm_private_data = vml;
>  	vml->vma = vma;
>  	list_add(&vml->head, &arena->vma_list);
> @@ -336,9 +334,28 @@ static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)
>  
>  static void arena_vm_open(struct vm_area_struct *vma)
>  {
> +	struct bpf_map *map = vma->vm_file->private_data;
> +	struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
>  	struct vma_list *vml = vma->vm_private_data;
>  
> -	refcount_inc(&vml->mmap_count);
> +	/*
> +	 * If vm_private_data points to a vma_list for a different VMA, it was
> +	 * inherited via vm_area_dup (fork or split). Clear it and allocate a

Arena mappings should never be split because that throws all the
arithmetic in arena_vm_fault() off (the pgoff for the mapping to
the right is now counted from the split point). We need to implement
may_split() for the vma struct ops and just return -EINVAL to prevent it.

> +	 * fresh entry for this VMA, following the HugeTLB vma_lock pattern.

Remove references to the "HugeTLB pattern".

> +	 */
> +	if (vml && vml->vma != vma)
> +		vma->vm_private_data = NULL;
> +
> +	if (vma->vm_private_data)
> +		return;
> +
> +	vml = kmalloc_obj(*vml);
> +	if (!vml)
> +		return;

The following is exactly remember_vma()'s code, reuse it.

> +	vml->vma = vma;
> +	vma->vm_private_data = vml;
> +	guard(mutex)(&arena->lock);
> +	list_add(&vml->head, &arena->vma_list);
>  }
>  

> [snip]


Also go to sashiko.dev and address/explain the feedback from there, it
seems valid. 

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

end of thread, other threads:[~2026-04-11 17:57 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-11 11:29 [PATCH bpf v2 0/2] bpf: Fix arena VMA use-after-free on fork Weiming Shi
2026-04-11 11:29 ` [PATCH bpf v2 1/2] bpf: Fix use-after-free of arena VMA " Weiming Shi
2026-04-11 17:57   ` Emil Tsalapatis
2026-04-11 11:29 ` [PATCH bpf v2 2/2] selftests/bpf: Add test for arena VMA use-after-free " Weiming Shi
2026-04-11 17:33   ` Emil Tsalapatis

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