Linux Kernel Selftest development
 help / color / mirror / Atom feed
From: Daniel Hodges <git@danielhodges.dev>
To: bpf@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
	ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
	martin.lau@linux.dev, eddyz87@gmail.com, memxor@gmail.com,
	song@kernel.org, yonghong.song@linux.dev, jolsa@kernel.org,
	shuah@kernel.org, git@danielhodges.dev, brho@google.com,
	hodgesd@meta.com
Subject: [PATCH 2/2] selftests/bpf: Add test validating arena VMA tracking across fork
Date: Wed, 13 May 2026 15:13:22 -0400	[thread overview]
Message-ID: <20260513191322.21319-2-git@danielhodges.dev> (raw)
In-Reply-To: <20260513191322.21319-1-git@danielhodges.dev>

Validate that arena page frees don't trigger a use-after-free when a
forked child holds an inherited arena mmap. The test allocates arena
pages, forks, munmaps the arena in the parent, then frees the arena
pages via BPF. With KASAN enabled, any stale VMA dereference in
zap_pages() will be caught.

Signed-off-by: Daniel Hodges <git@danielhodges.dev>
Assisted-by: Claude-Code:claude-opus-4-6
---
 .../bpf/prog_tests/arena_fork_free.c          | 114 ++++++++++++++++++
 .../selftests/bpf/progs/arena_fork_free.c     |  71 +++++++++++
 2 files changed, 185 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_fork_free.c
 create mode 100644 tools/testing/selftests/bpf/progs/arena_fork_free.c

diff --git a/tools/testing/selftests/bpf/prog_tests/arena_fork_free.c b/tools/testing/selftests/bpf/prog_tests/arena_fork_free.c
new file mode 100644
index 000000000000..6a6e1d4197cf
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_fork_free.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+/*
+ * Validate arena VMA tracking across fork.
+ *
+ * When a process with an arena mmap forks, each VMA must be independently
+ * tracked. This test verifies that freeing arena pages after the parent
+ * munmaps does not access stale VMA pointers.
+ *
+ * Sequence:
+ *   1. Load arena, allocate pages via BPF
+ *   2. fork() — child inherits arena mmap
+ *   3. Parent: munmap the arena
+ *   4. Parent: free arena pages via BPF — zap_pages() must only touch
+ *      live VMAs
+ *
+ * With KASAN enabled, any stale VMA access will be caught.
+ */
+#include <test_progs.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <sys/user.h>
+#ifndef PAGE_SIZE
+#define PAGE_SIZE getpagesize()
+#endif
+#include "arena_fork_free.skel.h"
+
+void test_arena_fork_free(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct arena_fork_free *skel;
+	int pipe_fds[2] = {-1, -1};
+	size_t arena_sz;
+	void *arena_area;
+	pid_t child;
+	int ret, status;
+	char buf;
+
+	skel = arena_fork_free__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "open_and_load"))
+		return;
+
+	/* Step 1: allocate arena pages via BPF */
+	ret = bpf_prog_test_run_opts(
+		bpf_program__fd(skel->progs.arena_alloc), &opts);
+	if (!ASSERT_OK(ret, "alloc_run"))
+		goto out;
+	if (!ASSERT_OK(opts.retval, "alloc_retval"))
+		goto out;
+	if (skel->bss->skip) {
+		printf("%s:SKIP:compiler doesn't support arena_cast\n",
+		       __func__);
+		test__skip();
+		goto out;
+	}
+
+	arena_area = bpf_map__initial_value(skel->maps.arena, &arena_sz);
+	if (!ASSERT_OK_PTR(arena_area, "arena_area"))
+		goto out;
+	arena_sz = bpf_map__max_entries(skel->maps.arena) * PAGE_SIZE;
+
+	if (!ASSERT_OK(pipe(pipe_fds), "pipe"))
+		goto out;
+
+	/* Step 2: fork — child inherits arena mmap */
+	child = fork();
+	if (!ASSERT_GE(child, 0, "fork")) {
+		close(pipe_fds[0]);
+		close(pipe_fds[1]);
+		goto out;
+	}
+
+	if (child == 0) {
+		/* Child: keep arena mmap alive, wait for parent to signal */
+		close(pipe_fds[1]);
+		read(pipe_fds[0], &buf, 1);
+		close(pipe_fds[0]);
+		_exit(0);
+	}
+
+	/* Parent continues */
+	close(pipe_fds[0]);
+	pipe_fds[0] = -1;
+
+	/* Step 3: munmap the arena in the parent */
+	ret = munmap(arena_area, arena_sz);
+	if (!ASSERT_OK(ret, "munmap"))
+		goto signal_child;
+
+	/*
+	 * Step 4: free arena pages via BPF.
+	 *
+	 * Wait for the RCU grace period so the parent's VMA slab memory
+	 * is actually freed (VMA freeing is deferred via call_rcu).
+	 * This ensures KASAN can detect any stale VMA dereference in
+	 * zap_pages().
+	 */
+	usleep(200000);
+	opts.retval = 0;
+	ret = bpf_prog_test_run_opts(
+		bpf_program__fd(skel->progs.arena_free), &opts);
+	ASSERT_OK(ret, "free_run");
+	ASSERT_OK(opts.retval, "free_retval");
+
+signal_child:
+	close(pipe_fds[1]);
+	pipe_fds[1] = -1;
+	waitpid(child, &status, 0);
+	ASSERT_TRUE(WIFEXITED(status), "child_exited");
+
+out:
+	arena_fork_free__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_fork_free.c b/tools/testing/selftests/bpf/progs/arena_fork_free.c
new file mode 100644
index 000000000000..81b4f9a4e94b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_fork_free.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+/*
+ * Validate arena VMA tracking across fork.
+ *
+ * Provides BPF programs to allocate and free arena pages, exercised by
+ * the userspace test to verify that zap_pages() correctly handles VMA
+ * lifecycle when a forked child holds an inherited arena mmap.
+ */
+#include "vmlinux.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, 10); /* 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");
+
+bool skip = false;
+
+#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
+
+void __arena *alloc_ptr;
+int alloc_page_cnt;
+
+SEC("syscall")
+int arena_alloc(void *ctx)
+{
+	alloc_ptr = bpf_arena_alloc_pages(&arena, NULL, 2, NUMA_NO_NODE, 0);
+	if (!alloc_ptr)
+		return 1;
+	alloc_page_cnt = 2;
+	return 0;
+}
+
+SEC("syscall")
+int arena_free(void *ctx)
+{
+	if (!alloc_ptr || !alloc_page_cnt)
+		return 1;
+	bpf_arena_free_pages(&arena, alloc_ptr, alloc_page_cnt);
+	alloc_ptr = NULL;
+	alloc_page_cnt = 0;
+	return 0;
+}
+
+#else
+
+SEC("syscall")
+int arena_alloc(void *ctx)
+{
+	skip = true;
+	return 0;
+}
+
+SEC("syscall")
+int arena_free(void *ctx)
+{
+	skip = true;
+	return 0;
+}
+
+#endif
+
+char _license[] SEC("license") = "GPL";
-- 
2.52.0


      reply	other threads:[~2026-05-13 19:13 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-13 19:13 [PATCH 1/2] bpf: arena: fix use-after-free in VMA tracking on fork Daniel Hodges
2026-05-13 19:13 ` Daniel Hodges [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260513191322.21319-2-git@danielhodges.dev \
    --to=git@danielhodges.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=brho@google.com \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=hodgesd@meta.com \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox