| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/25 09:41 | flow | patch-triage |
4mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"MEMCG"
],
"FocusSymbols": [
"arena_vm_fault",
"bpf_map_alloc_page_sleepable"
],
"KMSANReasoning": "The patch modifies the BPF arena page fault handler (`arena_vm_fault`) to preallocate pages using a sleepable allocator (`bpf_map_alloc_page_sleepable`) outside of a spinlock. This allows the memcg OOM killer to run if necessary. The new allocator explicitly uses the `__GFP_ZERO` flag (via `BPF_PAGE_GFP`), ensuring that all newly allocated pages are fully zero-initialized before being mapped into the arena. The patch does not introduce any new structures copied to userspace, nor does it alter control flow in a way that could expose uninitialized stack or heap memory. Any potential bugs introduced by these changes (e.g., memory leaks, use-after-free, or locking issues) would be effectively caught by KASAN, LOCKDEP, and standard kernel bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the page fault handler for BPF arenas (`arena_vm_fault`) to use a sleepable page allocator (`bpf_map_alloc_page_sleepable`) outside of the spinlock. This allows the memcg OOM killer to run if memory limits are exceeded, preventing spurious SIGBUS/SIGSEGV errors. These are reachable core kernel changes in the BPF subsystem.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/25 09:41 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 3b323e35fccc7e8cd716fa2778b064397c737eb8\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 25 09:41:54 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/bpf.h b/include/linux/bpf.h\nindex b3cd28d9e3b55..c817c99d29066 100644\n--- a/include/linux/bpf.h\n+++ b/include/linux/bpf.h\n@@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);\n \n int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\n \t\t\tunsigned long nr_pages, struct page **page_array);\n+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);\n #ifdef CONFIG_MEMCG\n void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,\n \t\t\t struct mem_cgroup **new_memcg);\ndiff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c\nindex 7b6847200b431..fa462a0ff1ad6 100644\n--- a/kernel/bpf/arena.c\n+++ b/kernel/bpf/arena.c\n@@ -481,7 +481,8 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n \tstruct bpf_map *map = vmf-\u003evma-\u003evm_file-\u003eprivate_data;\n \tstruct bpf_arena *arena = container_of(map, struct bpf_arena, map);\n \tstruct mem_cgroup *new_memcg, *old_memcg;\n-\tstruct page *page;\n+\tstruct page *page, *new_page = NULL;\n+\tvm_fault_t fault_ret;\n \tlong kbase, kaddr;\n \tunsigned long flags;\n \tint ret;\n@@ -489,59 +490,106 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n \tkbase = bpf_arena_get_kern_vm_start(arena);\n \tkaddr = kbase + (u32)(vmf-\u003eaddress);\n \n-\tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags))\n+\tpage = vmalloc_to_page((void *)kaddr);\n+\tif (!page \u0026\u0026 !(arena-\u003emap.map_flags \u0026 BPF_F_SEGV_ON_FAULT)) {\n+\t\t/*\n+\t\t * Preallocate outside the lock with a sleepable allocator so it\n+\t\t * can reclaim and run the memcg OOM killer, which the\n+\t\t * non-blocking allocator under arena-\u003espinlock cannot. A NULL\n+\t\t * return is non-recoverable, so fail with VM_FAULT_SIGBUS;\n+\t\t * VM_FAULT_OOM would be retried by the fault path and can\n+\t\t * livelock when the charged memcg is not the faulting task's.\n+\t\t */\n+\t\tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\n+\t\tnew_page = bpf_map_alloc_page_sleepable(map);\n+\t\tbpf_map_memcg_exit(old_memcg, new_memcg);\n+\t\tif (!new_page)\n+\t\t\treturn VM_FAULT_SIGBUS;\n+\t}\n+\n+\tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags)) {\n \t\t/*\n \t\t * A failed lock means a possible deadlock was detected. Don't\n \t\t * return VM_FAULT_RETRY: this handler never took mmap_lock, but\n \t\t * the fault path would re-take it on retry and deadlock. Fail.\n \t\t */\n+\t\tif (new_page)\n+\t\t\tfree_pages_nolock(new_page, 0);\n \t\treturn VM_FAULT_SIGBUS;\n+\t}\n \n \tpage = vmalloc_to_page((void *)kaddr);\n \tif (page) {\n-\t\tif (page == arena-\u003escratch_page)\n-\t\t\t/* BPF triggered scratch here; don't lazy-alloc over it */\n-\t\t\tgoto out_sigsegv;\n+\t\tif (page == arena-\u003escratch_page) {\n+\t\t\t/*\n+\t\t\t * A scratch page marks a hole. Segfault only if the user\n+\t\t\t * asked for it; otherwise we could lazy-allocate but\n+\t\t\t * choose not to over a hole, so report a bus error.\n+\t\t\t */\n+\t\t\tfault_ret = (arena-\u003emap.map_flags \u0026 BPF_F_SEGV_ON_FAULT) ?\n+\t\t\t\t VM_FAULT_SIGSEGV : VM_FAULT_SIGBUS;\n+\t\t\tgoto out_err_locked;\n+\t\t}\n \t\t/* already have a page vmap-ed */\n \t\tgoto out;\n \t}\n \n+\tif (arena-\u003emap.map_flags \u0026 BPF_F_SEGV_ON_FAULT) {\n+\t\t/* User space requested to segfault when page is not allocated by bpf prog */\n+\t\tfault_ret = VM_FAULT_SIGSEGV;\n+\t\tgoto out_err_locked;\n+\t}\n+\n \tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\n \n-\tif (arena-\u003emap.map_flags \u0026 BPF_F_SEGV_ON_FAULT)\n-\t\t/* User space requested to segfault when page is not allocated by bpf prog */\n-\t\tgoto out_sigsegv_memcg;\n+\tif (!new_page) {\n+\t\t/*\n+\t\t * Very rare race: the bpf program had allocated a page here, so\n+\t\t * the lockless probe saw it and we skipped preallocation, but it\n+\t\t * freed the page before we took the lock. Now we do need one;\n+\t\t * sleeping is not allowed here, so fall back to the non-blocking\n+\t\t * allocator and give up if it fails.\n+\t\t */\n+\t\tret = bpf_map_alloc_pages(map, map-\u003enuma_node, 1, \u0026new_page);\n+\t\tif (ret) {\n+\t\t\tfault_ret = VM_FAULT_SIGBUS;\n+\t\t\tgoto out_err_locked_memcg;\n+\t\t}\n+\t}\n \n \tret = range_tree_clear(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n-\tif (ret)\n-\t\tgoto out_sigsegv_memcg;\n-\n-\tstruct apply_range_data data = { .arena = arena, .pages = \u0026page, .i = 0 };\n-\t/* Account into memcg of the process that created bpf_arena */\n-\tret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, \u0026page);\n \tif (ret) {\n-\t\trange_tree_set(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n-\t\tgoto out_sigsegv_memcg;\n+\t\tfault_ret = VM_FAULT_SIGBUS;\n+\t\tgoto out_err_locked_memcg;\n \t}\n+\tstruct apply_range_data data = { .arena = arena, .pages = \u0026new_page, .i = 0 };\n \n \tret = apply_to_page_range(\u0026init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, \u0026data);\n \tif (ret) {\n \t\trange_tree_set(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n-\t\tfree_pages_nolock(page, 0);\n-\t\tgoto out_sigsegv_memcg;\n+\t\tfault_ret = VM_FAULT_SIGBUS;\n+\t\tgoto out_err_locked_memcg;\n \t}\n \tflush_vmap_cache(kaddr, PAGE_SIZE);\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n+\t/* new_page was consumed */\n+\tpage = new_page;\n+\tnew_page = NULL;\n out:\n \tpage_ref_add(page, 1);\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\tif (new_page)\n+\t\tfree_pages_nolock(new_page, 0);\n \tvmf-\u003epage = page;\n \treturn 0;\n-out_sigsegv_memcg:\n+\n+out_err_locked_memcg:\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n-out_sigsegv:\n+out_err_locked:\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n-\treturn VM_FAULT_SIGSEGV;\n+\tif (new_page)\n+\t\tfree_pages_nolock(new_page, 0);\n+\treturn fault_ret;\n }\n \n static const struct vm_operations_struct arena_vm_ops = {\ndiff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c\nindex 6874ba1424af0..f9b81638e537a 100644\n--- a/kernel/bpf/syscall.c\n+++ b/kernel/bpf/syscall.c\n@@ -602,15 +602,14 @@ static bool can_alloc_pages(void)\n \t\t!IS_ENABLED(CONFIG_PREEMPT_RT);\n }\n \n+#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)\n+\n static struct page *__bpf_alloc_page(int nid)\n {\n \tif (!can_alloc_pages())\n \t\treturn alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);\n \n-\treturn alloc_pages_node(nid,\n-\t\t\t\tGFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT\n-\t\t\t\t| __GFP_NOWARN,\n-\t\t\t\t0);\n+\treturn alloc_pages_node(nid, BPF_PAGE_GFP, 0);\n }\n \n int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\n@@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\n \treturn ret;\n }\n \n+/*\n+ * For callers that know they run in a sleepable context, e.g. a user page\n+ * fault handler. can_alloc_pages() is a conservative guess made for BPF\n+ * program context - notably it is always false on PREEMPT_RT - so going\n+ * through bpf_map_alloc_pages() there would needlessly pick the\n+ * non-blocking allocator, which never reclaims and never engages the OOM\n+ * machinery.\n+ */\n+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)\n+{\n+\tmight_sleep();\n+\treturn alloc_pages_node(map-\u003enuma_node, BPF_PAGE_GFP, 0);\n+}\n+\n static int btf_field_cmp(const void *a, const void *b)\n {\n \tconst struct btf_field *f1 = a, *f2 = b;\ndiff --git a/tools/testing/selftests/bpf/cgroup_helpers.c b/tools/testing/selftests/bpf/cgroup_helpers.c\nindex 45cd0b479fe35..4183ff6150c28 100644\n--- a/tools/testing/selftests/bpf/cgroup_helpers.c\n+++ b/tools/testing/selftests/bpf/cgroup_helpers.c\n@@ -188,6 +188,73 @@ int write_cgroup_file_parent(const char *relative_path, const char *file,\n \treturn __write_cgroup_file(cgroup_path, file, buf);\n }\n \n+static int __read_cgroup_file(const char *cgroup_path, const char *file,\n+\t\t\t char *buf, size_t len)\n+{\n+\tchar file_path[PATH_MAX + 1];\n+\tssize_t got;\n+\tint fd;\n+\n+\tsnprintf(file_path, sizeof(file_path), \"%s/%s\", cgroup_path, file);\n+\tfd = open(file_path, O_RDONLY);\n+\tif (fd \u003c 0) {\n+\t\tlog_err(\"Opening %s\", file_path);\n+\t\treturn 1;\n+\t}\n+\n+\tgot = read(fd, buf, len - 1);\n+\tif (got \u003c 0) {\n+\t\tlog_err(\"Reading %s\", file_path);\n+\t\tclose(fd);\n+\t\treturn 1;\n+\t}\n+\tbuf[got] = '\\0';\n+\tclose(fd);\n+\treturn 0;\n+}\n+\n+/**\n+ * read_cgroup_file() - Read from a cgroup file\n+ * @relative_path: The cgroup path, relative to the workdir\n+ * @file: The name of the file in cgroupfs to read from\n+ * @buf: Buffer to read into, NUL-terminated on success\n+ * @len: Size of @buf\n+ *\n+ * Read from a file in the given cgroup's directory.\n+ *\n+ * If successful, 0 is returned.\n+ */\n+int read_cgroup_file(const char *relative_path, const char *file,\n+\t\t char *buf, size_t len)\n+{\n+\tchar cgroup_path[PATH_MAX - 24];\n+\n+\tformat_cgroup_path(cgroup_path, relative_path);\n+\treturn __read_cgroup_file(cgroup_path, file, buf, len);\n+}\n+\n+/**\n+ * read_cgroup_file_parent() - Read from a cgroup file in the parent process\n+ * workdir\n+ * @relative_path: The cgroup path, relative to the parent process workdir\n+ * @file: The name of the file in cgroupfs to read from\n+ * @buf: Buffer to read into, NUL-terminated on success\n+ * @len: Size of @buf\n+ *\n+ * Read from a file in the given cgroup's directory under the parent process\n+ * workdir.\n+ *\n+ * If successful, 0 is returned.\n+ */\n+int read_cgroup_file_parent(const char *relative_path, const char *file,\n+\t\t\t char *buf, size_t len)\n+{\n+\tchar cgroup_path[PATH_MAX - 24];\n+\n+\tformat_parent_cgroup_path(cgroup_path, relative_path);\n+\treturn __read_cgroup_file(cgroup_path, file, buf, len);\n+}\n+\n /**\n * setup_cgroup_environment() - Setup the cgroup environment\n *\ndiff --git a/tools/testing/selftests/bpf/cgroup_helpers.h b/tools/testing/selftests/bpf/cgroup_helpers.h\nindex 3857304be8741..d42d2e13044e5 100644\n--- a/tools/testing/selftests/bpf/cgroup_helpers.h\n+++ b/tools/testing/selftests/bpf/cgroup_helpers.h\n@@ -15,6 +15,10 @@ int write_cgroup_file(const char *relative_path, const char *file,\n \t\t const char *buf);\n int write_cgroup_file_parent(const char *relative_path, const char *file,\n \t\t\t const char *buf);\n+int read_cgroup_file(const char *relative_path, const char *file,\n+\t\t char *buf, size_t len);\n+int read_cgroup_file_parent(const char *relative_path, const char *file,\n+\t\t\t char *buf, size_t len);\n int cgroup_setup_and_join(const char *relative_path);\n int get_root_cgroup(void);\n int create_and_get_cgroup(const char *relative_path);\ndiff --git a/tools/testing/selftests/bpf/prog_tests/arena_memcg.c b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c\nnew file mode 100644\nindex 0000000000000..c57b98494cfab\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c\n@@ -0,0 +1,158 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003ctest_progs.h\u003e\n+#include \u003cfcntl.h\u003e\n+#include \u003csignal.h\u003e\n+#include \u003csys/mman.h\u003e\n+#include \u003csys/wait.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003csys/user.h\u003e\n+#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */\n+#include \u003cunistd.h\u003e\n+#define PAGE_SIZE getpagesize()\n+#endif\n+\n+#include \"cgroup_helpers.h\"\n+#include \"arena_memcg.skel.h\"\n+\n+#define CG_PATH\t\t\"/arena_memcg\"\n+\n+/* Budget the arena gets on top of whatever is already charged after load. */\n+#define ARENA_BUDGET\t(64 * 1024 * 1024)\n+\n+static void dump_memcg(int (*rd)(const char *, const char *, char *, size_t))\n+{\n+\tchar buf[512];\n+\n+\t/*\n+\t * memory.current reads 0 once the child has left the cgroup, so it only\n+\t * carries information when dumped from the live child; memory.peak and\n+\t * memory.events survive the child and tell the story either way.\n+\t */\n+\tif (!rd(CG_PATH, \"memory.current\", buf, sizeof(buf)))\n+\t\tfprintf(stderr, \"memory.current: %s\", buf);\n+\tif (!rd(CG_PATH, \"memory.max\", buf, sizeof(buf)))\n+\t\tfprintf(stderr, \"memory.max: %s\", buf);\n+\tif (!rd(CG_PATH, \"memory.peak\", buf, sizeof(buf)))\n+\t\tfprintf(stderr, \"memory.peak: %s\", buf);\n+\tif (!rd(CG_PATH, \"memory.events\", buf, sizeof(buf)))\n+\t\tfprintf(stderr, \"memory.events:\\n%s\", buf);\n+\tfflush(stderr);\n+}\n+\n+/* Read one key from a flat keyed cgroup file, e.g. \"oom_kill\" in memory.events. */\n+static long cg_read_key(const char *cg, const char *file, const char *key)\n+{\n+\tchar buf[512], *p;\n+\n+\tif (read_cgroup_file(cg, file, buf, sizeof(buf)))\n+\t\treturn -1;\n+\tp = strstr(buf, key);\n+\tif (!p)\n+\t\treturn -1;\n+\treturn strtol(p + strlen(key), NULL, 10);\n+}\n+\n+void serial_test_arena_memcg(void)\n+{\n+\tint cgroup_fd = -1, status, err;\n+\tconst long ps = PAGE_SIZE;\n+\tchar buf[64];\n+\tpid_t pid;\n+\n+\terr = setup_cgroup_environment();\n+\tif (!ASSERT_OK(err, \"setup_cgroup_environment\"))\n+\t\treturn;\n+\n+\tcgroup_fd = create_and_get_cgroup(CG_PATH);\n+\tif (!ASSERT_OK_FD(cgroup_fd, \"create_and_get_cgroup\"))\n+\t\tgoto out;\n+\n+\t/* No memory controller -\u003e nothing to test. */\n+\tif (read_cgroup_file(CG_PATH, \"memory.current\", buf, sizeof(buf))) {\n+\t\tfprintf(stderr, \"%s:SKIP:no memory controller\\n\", __func__);\n+\t\ttest__skip();\n+\t\tgoto out;\n+\t}\n+\n+\tpid = fork();\n+\tif (!ASSERT_GE(pid, 0, \"fork\"))\n+\t\tgoto out;\n+\tif (pid == 0) {\n+\t\tstruct arena_memcg *cskel;\n+\t\t__u32 i, npages;\n+\t\tchar *base;\n+\t\tsize_t sz;\n+\t\tlong cur;\n+\n+\t\t/*\n+\t\t * Do everything from the child: the arena vma is VM_DONTCOPY so\n+\t\t * it would not survive fork(), only the child should be under the\n+\t\t * limit so that a memcg OOM cannot pick test_progs, and a map is\n+\t\t * charged to the memcg of the task that creates it - so join\n+\t\t * before load. The cgroup work dir belongs to the parent that set\n+\t\t * the environment up, so reach it with the _parent() helpers.\n+\t\t * Errors are reported to the parent through the exit code, since\n+\t\t * ASSERT_* in a forked child does not reach it.\n+\t\t */\n+\t\tsnprintf(buf, sizeof(buf), \"%d\", getpid());\n+\t\tif (write_cgroup_file_parent(CG_PATH, \"cgroup.procs\", buf))\n+\t\t\t_exit(2);\n+\n+\t\tcskel = arena_memcg__open_and_load();\n+\t\tif (!cskel)\n+\t\t\t_exit(3);\n+\n+\t\tbase = bpf_map__initial_value(cskel-\u003emaps.arena, \u0026sz);\n+\t\tif (!base)\n+\t\t\t_exit(4);\n+\t\tnpages = bpf_map__max_entries(cskel-\u003emaps.arena);\n+\n+\t\t/*\n+\t\t * Cap only now, after load: everything but the fault-in is\n+\t\t * charged, so the arena gets a fixed budget regardless of what\n+\t\t * the load itself cost, and the load can never hit the limit.\n+\t\t */\n+\t\tif (read_cgroup_file_parent(CG_PATH, \"memory.current\", buf, sizeof(buf)))\n+\t\t\t_exit(5);\n+\t\tcur = strtol(buf, NULL, 10);\n+\t\tsnprintf(buf, sizeof(buf), \"%ld\", cur + ARENA_BUDGET);\n+\t\tif (write_cgroup_file_parent(CG_PATH, \"memory.max\", buf))\n+\t\t\t_exit(6);\n+\n+\t\tfor (i = 0; i \u003c npages; i++)\n+\t\t\tbase[(size_t)i * ps] = 1;\n+\t\t/* Faulted everything without dying: dump why (only under -v). */\n+\t\tdump_memcg(read_cgroup_file_parent);\n+\t\t_exit(0);\n+\t}\n+\n+\tif (!ASSERT_EQ(waitpid(pid, \u0026status, 0), pid, \"waitpid\"))\n+\t\tgoto out;\n+\n+\t/* A non-zero exit means the child failed to set up; the code says where. */\n+\tif (WIFEXITED(status) \u0026\u0026 WEXITSTATUS(status)) {\n+\t\tASSERT_OK(WEXITSTATUS(status), \"child setup\");\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * Faulting a valid arena address until memory.max is hit must not look\n+\t * like an invalid access. Without the fix the fault path allocated with\n+\t * the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,\n+\t * and the child died with SIGSEGV on a valid address; now it is handled\n+\t * by the memcg OOM path instead. A SIGKILL alone would not prove the\n+\t * memcg OOM killer did it (a global OOM or an unrelated crash could also\n+\t * kill the child), so check memory.events.oom_kill, which records the\n+\t * memcg OOM and survives the child.\n+\t */\n+\tif (!ASSERT_TRUE(WIFSIGNALED(status), \"child killed by signal\"))\n+\t\tgoto out;\n+\tif (!ASSERT_GE(cg_read_key(CG_PATH, \"memory.events\", \"oom_kill\"), 1,\n+\t\t \"memcg oom_kill\"))\n+\t\tdump_memcg(read_cgroup_file);\n+out:\n+\tif (cgroup_fd \u003e= 0)\n+\t\tclose(cgroup_fd);\n+\tcleanup_cgroup_environment();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c\nnew file mode 100644\nindex 0000000000000..88259cfea06b6\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_memcg.c\n@@ -0,0 +1,24 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \"bpf_arena_common.h\"\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t__uint(max_entries, 50000); /* number of pages */\n+#ifdef __TARGET_ARCH_arm64\n+\t__ulong(map_extra, 0x1ull \u003c\u003c 32); /* start of mmap() region */\n+#else\n+\t__ulong(map_extra, 0x1ull \u003c\u003c 44); /* start of mmap() region */\n+#endif\n+} arena SEC(\".maps\");\n+\n+SEC(\"syscall\")\n+int noop(void *ctx)\n+{\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n"
} |
| 2/1 | 2026/08/25 09:41 | agent | patch-evaluator |
4mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"MEMCG"
],
"FocusSymbols": [
"arena_vm_fault",
"bpf_map_alloc_page_sleepable"
],
"Reasoning": "The patch modifies the page fault handler for BPF arenas (`arena_vm_fault`) to use a sleepable page allocator (`bpf_map_alloc_page_sleepable`) outside of the spinlock. This allows the memcg OOM killer to run if memory limits are exceeded, preventing spurious SIGBUS/SIGSEGV errors. These are reachable core kernel changes in the BPF subsystem.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 3b323e35fccc7e8cd716fa2778b064397c737eb8
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 25 09:41:54 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b3cd28d9e3b55..c817c99d29066 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
unsigned long nr_pages, struct page **page_array);
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);
#ifdef CONFIG_MEMCG
void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg);
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..fa462a0ff1ad6 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -481,7 +481,8 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
struct bpf_map *map = vmf->vma->vm_file->private_data;
struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
struct mem_cgroup *new_memcg, *old_memcg;
- struct page *page;
+ struct page *page, *new_page = NULL;
+ vm_fault_t fault_ret;
long kbase, kaddr;
unsigned long flags;
int ret;
@@ -489,59 +490,106 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
kbase = bpf_arena_get_kern_vm_start(arena);
kaddr = kbase + (u32)(vmf->address);
- if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
+ page = vmalloc_to_page((void *)kaddr);
+ if (!page && !(arena->map.map_flags & BPF_F_SEGV_ON_FAULT)) {
+ /*
+ * Preallocate outside the lock with a sleepable allocator so it
+ * can reclaim and run the memcg OOM killer, which the
+ * non-blocking allocator under arena->spinlock cannot. A NULL
+ * return is non-recoverable, so fail with VM_FAULT_SIGBUS;
+ * VM_FAULT_OOM would be retried by the fault path and can
+ * livelock when the charged memcg is not the faulting task's.
+ */
+ bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
+ new_page = bpf_map_alloc_page_sleepable(map);
+ bpf_map_memcg_exit(old_memcg, new_memcg);
+ if (!new_page)
+ return VM_FAULT_SIGBUS;
+ }
+
+ if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
/*
* A failed lock means a possible deadlock was detected. Don't
* return VM_FAULT_RETRY: this handler never took mmap_lock, but
* the fault path would re-take it on retry and deadlock. Fail.
*/
+ if (new_page)
+ free_pages_nolock(new_page, 0);
return VM_FAULT_SIGBUS;
+ }
page = vmalloc_to_page((void *)kaddr);
if (page) {
- if (page == arena->scratch_page)
- /* BPF triggered scratch here; don't lazy-alloc over it */
- goto out_sigsegv;
+ if (page == arena->scratch_page) {
+ /*
+ * A scratch page marks a hole. Segfault only if the user
+ * asked for it; otherwise we could lazy-allocate but
+ * choose not to over a hole, so report a bus error.
+ */
+ fault_ret = (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) ?
+ VM_FAULT_SIGSEGV : VM_FAULT_SIGBUS;
+ goto out_err_locked;
+ }
/* already have a page vmap-ed */
goto out;
}
+ if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) {
+ /* User space requested to segfault when page is not allocated by bpf prog */
+ fault_ret = VM_FAULT_SIGSEGV;
+ goto out_err_locked;
+ }
+
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
- if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
- /* User space requested to segfault when page is not allocated by bpf prog */
- goto out_sigsegv_memcg;
+ if (!new_page) {
+ /*
+ * Very rare race: the bpf program had allocated a page here, so
+ * the lockless probe saw it and we skipped preallocation, but it
+ * freed the page before we took the lock. Now we do need one;
+ * sleeping is not allowed here, so fall back to the non-blocking
+ * allocator and give up if it fails.
+ */
+ ret = bpf_map_alloc_pages(map, map->numa_node, 1, &new_page);
+ if (ret) {
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
+ }
+ }
ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
- if (ret)
- goto out_sigsegv_memcg;
-
- struct apply_range_data data = { .arena = arena, .pages = &page, .i = 0 };
- /* Account into memcg of the process that created bpf_arena */
- ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
- range_tree_set(&arena->rt, vmf->pgoff, 1);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
+ struct apply_range_data data = { .arena = arena, .pages = &new_page, .i = 0 };
ret = apply_to_page_range(&init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, &data);
if (ret) {
range_tree_set(&arena->rt, vmf->pgoff, 1);
- free_pages_nolock(page, 0);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
flush_vmap_cache(kaddr, PAGE_SIZE);
bpf_map_memcg_exit(old_memcg, new_memcg);
+ /* new_page was consumed */
+ page = new_page;
+ new_page = NULL;
out:
page_ref_add(page, 1);
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+ if (new_page)
+ free_pages_nolock(new_page, 0);
vmf->page = page;
return 0;
-out_sigsegv_memcg:
+
+out_err_locked_memcg:
bpf_map_memcg_exit(old_memcg, new_memcg);
-out_sigsegv:
+out_err_locked:
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
- return VM_FAULT_SIGSEGV;
+ if (new_page)
+ free_pages_nolock(new_page, 0);
+ return fault_ret;
}
static const struct vm_operations_struct arena_vm_ops = {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6874ba1424af0..f9b81638e537a 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -602,15 +602,14 @@ static bool can_alloc_pages(void)
!IS_ENABLED(CONFIG_PREEMPT_RT);
}
+#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)
+
static struct page *__bpf_alloc_page(int nid)
{
if (!can_alloc_pages())
return alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);
- return alloc_pages_node(nid,
- GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT
- | __GFP_NOWARN,
- 0);
+ return alloc_pages_node(nid, BPF_PAGE_GFP, 0);
}
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
@@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
return ret;
}
+/*
+ * For callers that know they run in a sleepable context, e.g. a user page
+ * fault handler. can_alloc_pages() is a conservative guess made for BPF
+ * program context - notably it is always false on PREEMPT_RT - so going
+ * through bpf_map_alloc_pages() there would needlessly pick the
+ * non-blocking allocator, which never reclaims and never engages the OOM
+ * machinery.
+ */
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)
+{
+ might_sleep();
+ return alloc_pages_node(map->numa_node, BPF_PAGE_GFP, 0);
+}
+
static int btf_field_cmp(const void *a, const void *b)
{
const struct btf_field *f1 = a, *f2 = b;
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.c b/tools/testing/selftests/bpf/cgroup_helpers.c
index 45cd0b479fe35..4183ff6150c28 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.c
+++ b/tools/testing/selftests/bpf/cgroup_helpers.c
@@ -188,6 +188,73 @@ int write_cgroup_file_parent(const char *relative_path, const char *file,
return __write_cgroup_file(cgroup_path, file, buf);
}
+static int __read_cgroup_file(const char *cgroup_path, const char *file,
+ char *buf, size_t len)
+{
+ char file_path[PATH_MAX + 1];
+ ssize_t got;
+ int fd;
+
+ snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file);
+ fd = open(file_path, O_RDONLY);
+ if (fd < 0) {
+ log_err("Opening %s", file_path);
+ return 1;
+ }
+
+ got = read(fd, buf, len - 1);
+ if (got < 0) {
+ log_err("Reading %s", file_path);
+ close(fd);
+ return 1;
+ }
+ buf[got] = '\0';
+ close(fd);
+ return 0;
+}
+
+/**
+ * read_cgroup_file() - Read from a cgroup file
+ * @relative_path: The cgroup path, relative to the workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
+/**
+ * read_cgroup_file_parent() - Read from a cgroup file in the parent process
+ * workdir
+ * @relative_path: The cgroup path, relative to the parent process workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory under the parent process
+ * workdir.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_parent_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
/**
* setup_cgroup_environment() - Setup the cgroup environment
*
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.h b/tools/testing/selftests/bpf/cgroup_helpers.h
index 3857304be8741..d42d2e13044e5 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.h
+++ b/tools/testing/selftests/bpf/cgroup_helpers.h
@@ -15,6 +15,10 @@ int write_cgroup_file(const char *relative_path, const char *file,
const char *buf);
int write_cgroup_file_parent(const char *relative_path, const char *file,
const char *buf);
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len);
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len);
int cgroup_setup_and_join(const char *relative_path);
int get_root_cgroup(void);
int create_and_get_cgroup(const char *relative_path);
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_memcg.c b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
new file mode 100644
index 0000000000000..c57b98494cfab
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/user.h>
+#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */
+#include <unistd.h>
+#define PAGE_SIZE getpagesize()
+#endif
+
+#include "cgroup_helpers.h"
+#include "arena_memcg.skel.h"
+
+#define CG_PATH "/arena_memcg"
+
+/* Budget the arena gets on top of whatever is already charged after load. */
+#define ARENA_BUDGET (64 * 1024 * 1024)
+
+static void dump_memcg(int (*rd)(const char *, const char *, char *, size_t))
+{
+ char buf[512];
+
+ /*
+ * memory.current reads 0 once the child has left the cgroup, so it only
+ * carries information when dumped from the live child; memory.peak and
+ * memory.events survive the child and tell the story either way.
+ */
+ if (!rd(CG_PATH, "memory.current", buf, sizeof(buf)))
+ fprintf(stderr, "memory.current: %s", buf);
+ if (!rd(CG_PATH, "memory.max", buf, sizeof(buf)))
+ fprintf(stderr, "memory.max: %s", buf);
+ if (!rd(CG_PATH, "memory.peak", buf, sizeof(buf)))
+ fprintf(stderr, "memory.peak: %s", buf);
+ if (!rd(CG_PATH, "memory.events", buf, sizeof(buf)))
+ fprintf(stderr, "memory.events:\n%s", buf);
+ fflush(stderr);
+}
+
+/* Read one key from a flat keyed cgroup file, e.g. "oom_kill" in memory.events. */
+static long cg_read_key(const char *cg, const char *file, const char *key)
+{
+ char buf[512], *p;
+
+ if (read_cgroup_file(cg, file, buf, sizeof(buf)))
+ return -1;
+ p = strstr(buf, key);
+ if (!p)
+ return -1;
+ return strtol(p + strlen(key), NULL, 10);
+}
+
+void serial_test_arena_memcg(void)
+{
+ int cgroup_fd = -1, status, err;
+ const long ps = PAGE_SIZE;
+ char buf[64];
+ pid_t pid;
+
+ err = setup_cgroup_environment();
+ if (!ASSERT_OK(err, "setup_cgroup_environment"))
+ return;
+
+ cgroup_fd = create_and_get_cgroup(CG_PATH);
+ if (!ASSERT_OK_FD(cgroup_fd, "create_and_get_cgroup"))
+ goto out;
+
+ /* No memory controller -> nothing to test. */
+ if (read_cgroup_file(CG_PATH, "memory.current", buf, sizeof(buf))) {
+ fprintf(stderr, "%s:SKIP:no memory controller\n", __func__);
+ test__skip();
+ goto out;
+ }
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto out;
+ if (pid == 0) {
+ struct arena_memcg *cskel;
+ __u32 i, npages;
+ char *base;
+ size_t sz;
+ long cur;
+
+ /*
+ * Do everything from the child: the arena vma is VM_DONTCOPY so
+ * it would not survive fork(), only the child should be under the
+ * limit so that a memcg OOM cannot pick test_progs, and a map is
+ * charged to the memcg of the task that creates it - so join
+ * before load. The cgroup work dir belongs to the parent that set
+ * the environment up, so reach it with the _parent() helpers.
+ * Errors are reported to the parent through the exit code, since
+ * ASSERT_* in a forked child does not reach it.
+ */
+ snprintf(buf, sizeof(buf), "%d", getpid());
+ if (write_cgroup_file_parent(CG_PATH, "cgroup.procs", buf))
+ _exit(2);
+
+ cskel = arena_memcg__open_and_load();
+ if (!cskel)
+ _exit(3);
+
+ base = bpf_map__initial_value(cskel->maps.arena, &sz);
+ if (!base)
+ _exit(4);
+ npages = bpf_map__max_entries(cskel->maps.arena);
+
+ /*
+ * Cap only now, after load: everything but the fault-in is
+ * charged, so the arena gets a fixed budget regardless of what
+ * the load itself cost, and the load can never hit the limit.
+ */
+ if (read_cgroup_file_parent(CG_PATH, "memory.current", buf, sizeof(buf)))
+ _exit(5);
+ cur = strtol(buf, NULL, 10);
+ snprintf(buf, sizeof(buf), "%ld", cur + ARENA_BUDGET);
+ if (write_cgroup_file_parent(CG_PATH, "memory.max", buf))
+ _exit(6);
+
+ for (i = 0; i < npages; i++)
+ base[(size_t)i * ps] = 1;
+ /* Faulted everything without dying: dump why (only under -v). */
+ dump_memcg(read_cgroup_file_parent);
+ _exit(0);
+ }
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto out;
+
+ /* A non-zero exit means the child failed to set up; the code says where. */
+ if (WIFEXITED(status) && WEXITSTATUS(status)) {
+ ASSERT_OK(WEXITSTATUS(status), "child setup");
+ goto out;
+ }
+
+ /*
+ * Faulting a valid arena address until memory.max is hit must not look
+ * like an invalid access. Without the fix the fault path allocated with
+ * the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,
+ * and the child died with SIGSEGV on a valid address; now it is handled
+ * by the memcg OOM path instead. A SIGKILL alone would not prove the
+ * memcg OOM killer did it (a global OOM or an unrelated crash could also
+ * kill the child), so check memory.events.oom_kill, which records the
+ * memcg OOM and survives the child.
+ */
+ if (!ASSERT_TRUE(WIFSIGNALED(status), "child killed by signal"))
+ goto out;
+ if (!ASSERT_GE(cg_read_key(CG_PATH, "memory.events", "oom_kill"), 1,
+ "memcg oom_kill"))
+ dump_memcg(read_cgroup_file);
+out:
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ cleanup_cgroup_environment();
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c
new file mode 100644
index 0000000000000..88259cfea06b6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_memcg.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#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, 50000); /* 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");
+
+SEC("syscall")
+int noop(void *ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
|
| 3/2 | 2026/08/25 09:41 | llm | patch-evaluator |
2mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8222 output: 19 thoughts: 265 Thoughts: **Analyzing the BPF Arena Patch** Okay, so I'm looking at this patch that touches `kernel/bpf/arena.c` and `kernel/bpf/syscall.c`, and it looks like it's adding some selftests, which is good. But more importantly, it's changing how pages are allocated within the BPF arena, specifically in the `arena_vm_fault` function, the page fault handler. It looks like they're introducing `bpf_map_alloc_page_sleepable`, which is using a sleepable allocator now, potentially triggering the memcg OOM killer, instead of just the non-blocking allocator that was under a spinlock. My immediate thought is: is this even reachable? Is this change worth spending the effort to fuzz it? I need to figure out where these modified functions, `arena_vm_fault` and `bpf_map_alloc_page_sleepable`, are actually called. `arena_vm_fault` is, as I recall, the page fault handler for BPF arena maps. The other function, `bpf_map_alloc_page_sleepable`, is the new function that's *being* called from `arena_vm_fault`. Simple enough. Time to search the code. I need to find where `arena_vm_fault` and `bpf_map_alloc_page_sleepable` are used. Okay, `arena_vm_fault` is used in `arena_vm_ops`, which I bet is set somewhere within `arena_map_mmap`. I'll go check `kernel/bpf/arena.c` to confirm that. |
| 4/2 | 2026/08/25 09:44 | tool | grepper |
0mArgs:{
"Expression": "arena_vm_fault"
}Results: {
"Output": "kernel/bpf/arena.c=464=static void arena_vm_close(struct vm_area_struct *vma)\n--\nkernel/bpf/arena.c-478-\nkernel/bpf/arena.c:479:static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\nkernel/bpf/arena.c-480-{\n--\nkernel/bpf/arena.c=595=static const struct vm_operations_struct arena_vm_ops = {\n--\nkernel/bpf/arena.c-599-\t.close\t\t= arena_vm_close,\nkernel/bpf/arena.c:600:\t.fault = arena_vm_fault,\nkernel/bpf/arena.c-601-};\n--\ntools/testing/selftests/bpf/prog_tests/arena_mem_usage.c=48=void serial_test_arena_mem_usage(void)\n--\ntools/testing/selftests/bpf/prog_tests/arena_mem_usage.c-99-\t * User-space fault-in: touching unallocated arena pages allocates them\ntools/testing/selftests/bpf/prog_tests/arena_mem_usage.c:100:\t * through arena_vm_fault(). libbpf mmap()s the arena at map_extra during\ntools/testing/selftests/bpf/prog_tests/arena_mem_usage.c-101-\t * load, so bpf_map__initial_value() hands back that base.\n"
} |
| 5/2 | 2026/08/25 09:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9254 output: 20 thoughts: 60 Thoughts: **My Initial Thoughts on This Patch** Okay, so this patch is touching `arena_vm_fault`, which is the page fault handler specifically for BPF arenas. My immediate thought is, "Ah, adding `bpf_map_alloc_page_sleepable`. That's interesting, and definitely a function to be aware of." The fact that it's in the page fault handler makes it inherently reachable, and my fuzzing instincts are already kicking in. This is definitely a spot worth investing some time and effort to test. Now, I need to understand the config options related to these BPF arenas so I can ensure this functionality is properly enabled and I can build a proper test environment. Let me find those config options. |
| 6/2 | 2026/08/25 09:44 | tool | grepper |
0mError:no matches Args: {
"Expression": "config BPF_ARENA"
}Results: {
"error": "no matches"
} |
| 7/2 | 2026/08/25 09:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9501 output: 17 thoughts: 26 Thoughts: Here's my thought process, as if I were the one thinking it: **Digging into BPF Arena Configuration** Okay, so I need to figure out where the `BPF_ARENA` configuration option lives. It's not something I can just intuitively recall, so the first step is to dive into the kernel configuration files. My gut says it's probably defined in either `Kconfig` or `kernel/bpf/Makefile`, since that seems like the most logical location for a BPF-specific feature. I'll search for it there. This is a pretty standard debugging approach when dealing with kernel configuration options. Let's see what we find! |
| 8/2 | 2026/08/25 09:44 | tool | grepper |
0mArgs:{
"Expression": "arena\\.o"
}Results: {
"Output": "kernel/bpf/Makefile=19=ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\n--\nkernel/sched/ext/arena.c=71=static int scx_arena_grow(struct scx_sched *sch, u32 page_cnt)\n--\nkernel/sched/ext/arena.c-86-\tuaddr32 = (u32)(unsigned long)p;\nkernel/sched/ext/arena.c:87:\t/* arena.o, which defines these, is built only on MMU \u0026\u0026 64BIT */\nkernel/sched/ext/arena.c-88-#if defined(CONFIG_MMU) \u0026\u0026 defined(CONFIG_64BIT)\n--\nkernel/sched/ext/ext.c=8065=static int scx_arena_scan_prog(struct bpf_prog *prog, void *data)\n--\nkernel/sched/ext/ext.c-8069-\nkernel/sched/ext/ext.c:8070:\t/* arena.o, which defines these, is built only on MMU \u0026\u0026 64BIT */\nkernel/sched/ext/ext.c-8071-#if defined(CONFIG_MMU) \u0026\u0026 defined(CONFIG_64BIT)\n--\ntools/testing/selftests/bpf/Makefile=993=$(OUTPUT)/bench_xdp_lb.o: $(OUTPUT)/xdp_lb_bench.skel.h bench_bpf_timing.h\ntools/testing/selftests/bpf/Makefile:994:$(OUTPUT)/bench_libarena.o: $(LIBARENA_BENCH_SKEL)\ntools/testing/selftests/bpf/Makefile-995-$(OUTPUT)/bench_bpf_timing.o: bench_bpf_timing.h\n--\ntools/testing/selftests/bpf/Makefile=998=$(OUTPUT)/bench: $(OUTPUT)/bench.o \\\n--\ntools/testing/selftests/bpf/Makefile-1021-\t\t $(OUTPUT)/bench_xdp_lb.o \\\ntools/testing/selftests/bpf/Makefile:1022:\t\t $(OUTPUT)/bench_libarena.o \\\ntools/testing/selftests/bpf/Makefile-1023-\t\t $(OUTPUT)/usdt_1.o \\\n"
} |
| 9/2 | 2026/08/25 09:44 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10295 output: 26 thoughts: 320 Thoughts: **Analyzing the bpf arena code** Okay, so I'm looking at this `arena.o` object file, and it seems like it gets built only under specific configurations: `CONFIG_BPF_SYSCALL`, `CONFIG_MMU`, and `CONFIG_64BIT`. That makes sense; it's probably related to memory management within the BPF subsystem. The patch also touches `include/linux/bpf.h` and `kernel/bpf/syscall.c`, so there are likely related changes in the core BPF structures and the syscall interface. My initial focus symbols were `arena_vm_fault` and `bpf_map_alloc_page_sleepable`. Hold on, though... I need to double-check something. Is `arena_vm_fault` static? Yup, it's defined as `static vm_fault_t arena_vm_fault(struct vm_fault *vmf)`. Hm, that might be problematic. The guidance prefers non-static, non-inlined entry points. Static functions are often inlined, making them harder to target. However, it *is* used as a function pointer within `arena_vm_ops`, so it won't be inlined. It *should* have a distinct symbol address despite being static. But, the prompt suggests prioritizing non-static functions, so I should see if there are better candidates. `bpf_map_alloc_page_sleepable` is non-static, so let's look for other non-static functions in `kernel/bpf/arena.c`. |
| 10/2 | 2026/08/25 09:45 | tool | grepper |
0mError:no matches Args: {
"Expression": "^struct bpf_map \\*arena_map_alloc"
}Results: {
"error": "no matches"
} |
| 11/2 | 2026/08/25 09:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10994 output: 27 thoughts: 21 Thoughts: Here's my summary, as though I were thinking through the process: **Diving into `arena.c` for BPF Shenanigans** Okay, so I need to understand what's going on with memory allocation in the BPF arena. The first thing I'm going to do is get a high-level view of this specific arena implementation, so I'm going to check `kernel/bpf/arena.c`. My primary interest here is how functions are defined and used. I'm going to scan this file specifically looking for any *non-static* functions. I'm assuming the *static* ones are likely internal to the arena module. The non-static ones are what I want to understand: those are the entry points, the functions other parts of the kernel are calling into. I need to get a clear picture of the public interface that arena.c exposes. This will give me a great starting point. |
| 12/2 | 2026/08/25 09:45 | tool | grepper |
0mArgs:{
"Expression": "^[a-zA-Z_].* arena_map_"
}Results: {
"Output": "include/linux/bpf_types.h=134=BPF_MAP_TYPE(BPF_MAP_TYPE_USER_RINGBUF, user_ringbuf_map_ops)\ninclude/linux/bpf_types.h:135:BPF_MAP_TYPE(BPF_MAP_TYPE_ARENA, arena_map_ops)\ninclude/linux/bpf_types.h-136-BPF_MAP_TYPE(BPF_MAP_TYPE_INSN_ARRAY, insn_array_map_ops)\n--\nkernel/bpf/arena.c=110=struct bpf_map *bpf_prog_arena(struct bpf_prog *prog)\n--\nkernel/bpf/arena.c-116-\nkernel/bpf/arena.c:117:static long arena_map_peek_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-118-{\n--\nkernel/bpf/arena.c-121-\nkernel/bpf/arena.c:122:static long arena_map_push_elem(struct bpf_map *map, void *value, u64 flags)\nkernel/bpf/arena.c-123-{\n--\nkernel/bpf/arena.c-126-\nkernel/bpf/arena.c:127:static long arena_map_pop_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-128-{\n--\nkernel/bpf/arena.c-131-\nkernel/bpf/arena.c:132:static long arena_map_delete_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-133-{\n--\nkernel/bpf/arena.c-136-\nkernel/bpf/arena.c:137:static int arena_map_get_next_key(struct bpf_map *map, void *key, void *next_key)\nkernel/bpf/arena.c-138-{\n--\nkernel/bpf/arena.c=343=static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)\n--\nkernel/bpf/arena.c-369-\nkernel/bpf/arena.c:370:static void arena_map_free(struct bpf_map *map)\nkernel/bpf/arena.c-371-{\n--\nkernel/bpf/arena.c=401=static void *arena_map_lookup_elem(struct bpf_map *map, void *key)\n--\nkernel/bpf/arena.c-405-\nkernel/bpf/arena.c:406:static long arena_map_update_elem(struct bpf_map *map, void *key,\nkernel/bpf/arena.c-407-\t\t\t\t void *value, u64 flags)\n--\nkernel/bpf/arena.c-411-\nkernel/bpf/arena.c:412:static int arena_map_check_btf(struct bpf_map *map, const struct btf *btf,\nkernel/bpf/arena.c-413-\t\t\t const struct btf_type *key_type, const struct btf_type *value_type)\n--\nkernel/bpf/arena.c-417-\nkernel/bpf/arena.c:418:static u64 arena_map_mem_usage(const struct bpf_map *map)\nkernel/bpf/arena.c-419-{\n--\nkernel/bpf/arena.c=603=static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long addr,\n--\nkernel/bpf/arena.c-636-\nkernel/bpf/arena.c:637:static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\nkernel/bpf/arena.c-638-{\n--\nkernel/bpf/arena.c-676-\nkernel/bpf/arena.c:677:static int arena_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)\nkernel/bpf/arena.c-678-{\n--\nkernel/bpf/arena.c=687=BTF_ID_LIST_SINGLE(bpf_arena_map_btf_ids, struct, bpf_arena)\nkernel/bpf/arena.c:688:const struct bpf_map_ops arena_map_ops = {\nkernel/bpf/arena.c-689-\t.map_meta_equal = bpf_map_meta_equal,\n--\nkernel/bpf/core.c=3410=u64 bpf_check_timed_may_goto(struct bpf_timed_may_goto *p)\n--\nkernel/bpf/core.c-3428-/* for configs without MMU or 32-bit */\nkernel/bpf/core.c:3429:__weak const struct bpf_map_ops arena_map_ops;\nkernel/bpf/core.c-3430-__weak u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)\n"
} |
| 13/2 | 2026/08/25 09:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12565 output: 25 thoughts: 96 Thoughts: **Analyzing the Map Operations and Allocation Functions** Okay, so I'm looking at this code and immediately keying in on a few crucial points. The `arena_map_ops` structure, which I see is used for the map operations, is declared as non-static. That's the first thing to note. Now, the functions that have been changed appear to be `arena_vm_fault` and `bpf_map_alloc_page_sleepable`. `arena_vm_fault` is probably not the main issue here. The second function, `bpf_map_alloc_page_sleepable` , is also non-static; and seems to be exported, or at least it isn't static. My immediate thought is, "Is `bpf_map_alloc_page_sleepable` actually exported and visible?" I need to verify that. That's my next step. Let's dig deeper and confirm its visibility. |
| 14/2 | 2026/08/25 09:45 | tool | grepper |
0mArgs:{
"Expression": "bpf_map_alloc_page_sleepable"
}Results: {
"Output": "include/linux/bpf.h=2785=int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\ninclude/linux/bpf.h-2786-\t\t\tunsigned long nr_pages, struct page **page_array);\ninclude/linux/bpf.h:2787:struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);\ninclude/linux/bpf.h-2788-#ifdef CONFIG_MEMCG\n--\nkernel/bpf/arena.c=479=static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n--\nkernel/bpf/arena.c-503-\t\tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\nkernel/bpf/arena.c:504:\t\tnew_page = bpf_map_alloc_page_sleepable(map);\nkernel/bpf/arena.c-505-\t\tbpf_map_memcg_exit(old_memcg, new_memcg);\n--\nkernel/bpf/syscall.c=615=int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\n--\nkernel/bpf/syscall.c-645- */\nkernel/bpf/syscall.c:646:struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)\nkernel/bpf/syscall.c-647-{\n"
} |
| 15/2 | 2026/08/25 09:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13279 output: 19 thoughts: 90 Thoughts: **Analyzing BPF Map Allocation and VM Operations** Okay, so I'm looking at this code and thinking, `bpf_map_alloc_page_sleepable` isn't static. That's good to know. Now, `arena_vm_fault` is static, but hold on... it's a function pointer within `arena_vm_ops`. Since it's a function pointer it's probably not inlined. That's important for performance considerations. Wait a minute, I need to keep these straight. `arena_map_ops` handles the map operations and `arena_vm_ops` handles the VM operations. Right. Okay, let's refocus. I need to take a closer look at `arena_vm_ops`. I need to figure out how that's working. |
| 16/2 | 2026/08/25 09:45 | tool | grepper |
0mArgs:{
"Expression": "arena_vm_ops"
}Results: {
"Output": "kernel/bpf/arena.c=479=static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n--\nkernel/bpf/arena.c-594-\nkernel/bpf/arena.c:595:static const struct vm_operations_struct arena_vm_ops = {\nkernel/bpf/arena.c-596-\t.open\t\t= arena_vm_open,\n--\nkernel/bpf/arena.c=637=static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\n--\nkernel/bpf/arena.c-672-\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);\nkernel/bpf/arena.c:673:\tvma-\u003evm_ops = \u0026arena_vm_ops;\nkernel/bpf/arena.c-674-\treturn 0;\n"
} |
| 17/2 | 2026/08/25 09:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13806 output: 17 thoughts: 303 Thoughts: **My Reasoning for Selecting Focus Symbols** Okay, let's break this down. I'm looking for the best entry points for Syzkaller, given the constraints. `arena_vm_fault` is a callback, so it's not inlined, which is good. That gives it a distinct symbol address. It is also a static function, which could be an issue for Syzkaller if it can't parse kallsyms. However, the documentation stresses preferring non-static, non-inlined functions. `bpf_map_alloc_page_sleepable` is definitely non-static, so it should be easily targetable. And hold on... aren't we focusing on syscalls? Right, Syzkaller targets syscalls. The BPF syscall *is* the entry point! So, the goal is to target functions related to the BPF subsystem that are modified and reachable from it. Therefore, given the available information, I'll include `bpf_map_alloc_page_sleepable` and `arena_vm_fault` in my list of focus symbols. They are not inlined. Now, let's consider CONFIG options. `CONFIG_BPF_SYSCALL` is a must-have, obviously. The `arena.o` module specifically seems to require `CONFIG_MMU` and `CONFIG_64BIT` to be built. It's time to double-check `kernel/bpf/Makefile` to confirm all this. |
| 18/2 | 2026/08/25 09:45 | tool | grepper |
0mArgs:{
"Expression": "arena\\.o"
}Results: {
"Output": "kernel/bpf/Makefile=19=ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\nkernel/bpf/Makefile:20:obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\nkernel/bpf/Makefile-21-endif\n--\nkernel/sched/ext/arena.c=71=static int scx_arena_grow(struct scx_sched *sch, u32 page_cnt)\n--\nkernel/sched/ext/arena.c-86-\tuaddr32 = (u32)(unsigned long)p;\nkernel/sched/ext/arena.c:87:\t/* arena.o, which defines these, is built only on MMU \u0026\u0026 64BIT */\nkernel/sched/ext/arena.c-88-#if defined(CONFIG_MMU) \u0026\u0026 defined(CONFIG_64BIT)\n--\nkernel/sched/ext/ext.c=8065=static int scx_arena_scan_prog(struct bpf_prog *prog, void *data)\n--\nkernel/sched/ext/ext.c-8069-\nkernel/sched/ext/ext.c:8070:\t/* arena.o, which defines these, is built only on MMU \u0026\u0026 64BIT */\nkernel/sched/ext/ext.c-8071-#if defined(CONFIG_MMU) \u0026\u0026 defined(CONFIG_64BIT)\n--\ntools/testing/selftests/bpf/Makefile=993=$(OUTPUT)/bench_xdp_lb.o: $(OUTPUT)/xdp_lb_bench.skel.h bench_bpf_timing.h\ntools/testing/selftests/bpf/Makefile:994:$(OUTPUT)/bench_libarena.o: $(LIBARENA_BENCH_SKEL)\ntools/testing/selftests/bpf/Makefile-995-$(OUTPUT)/bench_bpf_timing.o: bench_bpf_timing.h\n--\ntools/testing/selftests/bpf/Makefile=998=$(OUTPUT)/bench: $(OUTPUT)/bench.o \\\n--\ntools/testing/selftests/bpf/Makefile-1021-\t\t $(OUTPUT)/bench_xdp_lb.o \\\ntools/testing/selftests/bpf/Makefile:1022:\t\t $(OUTPUT)/bench_libarena.o \\\ntools/testing/selftests/bpf/Makefile-1023-\t\t $(OUTPUT)/usdt_1.o \\\n"
} |
| 19/2 | 2026/08/25 09:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15053 output: 147 thoughts: 634 Thoughts: **My Analysis of Required Kernel Configurations** Okay, let's break this down. First, I know that for syzkaller targets (like x86_64 and arm64), `CONFIG_MMU` and `CONFIG_64BIT` are pretty much givens, so those are already assumed. The crucial part is figuring out if there are any *other* kernel configurations that are now necessary for this patch. The patch mentions `CONFIG_MEMCG` in `include/linux/bpf.h`, specifically within the `bpf_map_memcg_enter` function. Initially, I thought that `CONFIG_MEMCG` might only change behavior without being strictly required. But, the patch description explicitly highlights the use of a "sleepable allocator" to allow the memcg OOM killer to reclaim memory and run, which the existing non-blocking allocator couldn't. This strongly suggests `CONFIG_MEMCG` is directly tied to the functional change being made. Therefore, `MEMCG` is definitely required. The other obvious one is `BPF_SYSCALL`, because the code makes use of the BPF system call. Now, let's dig into `kernel/bpf/syscall.c` and specifically look at `bpf_map_alloc_page_sleepable`. The code shows `might_sleep()` and uses `alloc_pages_node`, which confirms this is unconditionally compiled when `CONFIG_BPF_SYSCALL` is enabled. So, to summarize, the essential configurations I need to consider are: `BPF_SYSCALL` and `MEMCG`. My focus in the code is going to be on the `bpf_map_alloc_page_sleepable` function and also `arena_vm_fault`. Double-checking that the second function name is correct, I've confirmed that `arena_vm_fault` is indeed the correct name as shown in the definition `static vm_fault_t arena_vm_fault(struct vm_fault *vmf)`. I also checked `bpf_map_alloc_pages` for other required symbols. The change to use `BPF_PAGE_GFP` is a minor implementation detail. So it's `arena_vm_fault` and `bpf_map_alloc_page_sleepable` that represent the meat of this code change. I'm ready to call set-results. |
| 20/2 | 2026/08/25 09:46 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"MEMCG"
],
"FocusSymbols": [
"arena_vm_fault",
"bpf_map_alloc_page_sleepable"
],
"Reasoning": "The patch modifies the page fault handler for BPF arenas (`arena_vm_fault`) to use a sleepable page allocator (`bpf_map_alloc_page_sleepable`) outside of the spinlock. This allows the memcg OOM killer to run if memory limits are exceeded, preventing spurious SIGBUS/SIGSEGV errors. These are reachable core kernel changes in the BPF subsystem.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"MEMCG"
],
"FocusSymbols": [
"arena_vm_fault",
"bpf_map_alloc_page_sleepable"
],
"Reasoning": "The patch modifies the page fault handler for BPF arenas (`arena_vm_fault`) to use a sleepable page allocator (`bpf_map_alloc_page_sleepable`) outside of the spinlock. This allows the memcg OOM killer to run if memory limits are exceeded, preventing spurious SIGBUS/SIGSEGV errors. These are reachable core kernel changes in the BPF subsystem.",
"WorthFuzzing": true
} |
| 21/1 | 2026/08/25 09:46 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 22/2 | 2026/08/25 09:46 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch modifies the BPF arena page fault handler (`arena_vm_fault`) to preallocate pages using a sleepable allocator (`bpf_map_alloc_page_sleepable`) outside of a spinlock. This allows the memcg OOM killer to run if necessary. The new allocator explicitly uses the `__GFP_ZERO` flag (via `BPF_PAGE_GFP`), ensuring that all newly allocated pages are fully zero-initialized before being mapped into the arena. The patch does not introduce any new structures copied to userspace, nor does it alter control flow in a way that could expose uninitialized stack or heap memory. Any potential bugs introduced by these changes (e.g., memory leaks, use-after-free, or locking issues) would be effectively caught by KASAN, LOCKDEP, and standard kernel bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit 3b323e35fccc7e8cd716fa2778b064397c737eb8
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 25 09:41:54 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b3cd28d9e3b55..c817c99d29066 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
unsigned long nr_pages, struct page **page_array);
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);
#ifdef CONFIG_MEMCG
void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg);
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..fa462a0ff1ad6 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -481,7 +481,8 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
struct bpf_map *map = vmf->vma->vm_file->private_data;
struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
struct mem_cgroup *new_memcg, *old_memcg;
- struct page *page;
+ struct page *page, *new_page = NULL;
+ vm_fault_t fault_ret;
long kbase, kaddr;
unsigned long flags;
int ret;
@@ -489,59 +490,106 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
kbase = bpf_arena_get_kern_vm_start(arena);
kaddr = kbase + (u32)(vmf->address);
- if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
+ page = vmalloc_to_page((void *)kaddr);
+ if (!page && !(arena->map.map_flags & BPF_F_SEGV_ON_FAULT)) {
+ /*
+ * Preallocate outside the lock with a sleepable allocator so it
+ * can reclaim and run the memcg OOM killer, which the
+ * non-blocking allocator under arena->spinlock cannot. A NULL
+ * return is non-recoverable, so fail with VM_FAULT_SIGBUS;
+ * VM_FAULT_OOM would be retried by the fault path and can
+ * livelock when the charged memcg is not the faulting task's.
+ */
+ bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
+ new_page = bpf_map_alloc_page_sleepable(map);
+ bpf_map_memcg_exit(old_memcg, new_memcg);
+ if (!new_page)
+ return VM_FAULT_SIGBUS;
+ }
+
+ if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
/*
* A failed lock means a possible deadlock was detected. Don't
* return VM_FAULT_RETRY: this handler never took mmap_lock, but
* the fault path would re-take it on retry and deadlock. Fail.
*/
+ if (new_page)
+ free_pages_nolock(new_page, 0);
return VM_FAULT_SIGBUS;
+ }
page = vmalloc_to_page((void *)kaddr);
if (page) {
- if (page == arena->scratch_page)
- /* BPF triggered scratch here; don't lazy-alloc over it */
- goto out_sigsegv;
+ if (page == arena->scratch_page) {
+ /*
+ * A scratch page marks a hole. Segfault only if the user
+ * asked for it; otherwise we could lazy-allocate but
+ * choose not to over a hole, so report a bus error.
+ */
+ fault_ret = (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) ?
+ VM_FAULT_SIGSEGV : VM_FAULT_SIGBUS;
+ goto out_err_locked;
+ }
/* already have a page vmap-ed */
goto out;
}
+ if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) {
+ /* User space requested to segfault when page is not allocated by bpf prog */
+ fault_ret = VM_FAULT_SIGSEGV;
+ goto out_err_locked;
+ }
+
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
- if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
- /* User space requested to segfault when page is not allocated by bpf prog */
- goto out_sigsegv_memcg;
+ if (!new_page) {
+ /*
+ * Very rare race: the bpf program had allocated a page here, so
+ * the lockless probe saw it and we skipped preallocation, but it
+ * freed the page before we took the lock. Now we do need one;
+ * sleeping is not allowed here, so fall back to the non-blocking
+ * allocator and give up if it fails.
+ */
+ ret = bpf_map_alloc_pages(map, map->numa_node, 1, &new_page);
+ if (ret) {
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
+ }
+ }
ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
- if (ret)
- goto out_sigsegv_memcg;
-
- struct apply_range_data data = { .arena = arena, .pages = &page, .i = 0 };
- /* Account into memcg of the process that created bpf_arena */
- ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
- range_tree_set(&arena->rt, vmf->pgoff, 1);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
+ struct apply_range_data data = { .arena = arena, .pages = &new_page, .i = 0 };
ret = apply_to_page_range(&init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, &data);
if (ret) {
range_tree_set(&arena->rt, vmf->pgoff, 1);
- free_pages_nolock(page, 0);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
flush_vmap_cache(kaddr, PAGE_SIZE);
bpf_map_memcg_exit(old_memcg, new_memcg);
+ /* new_page was consumed */
+ page = new_page;
+ new_page = NULL;
out:
page_ref_add(page, 1);
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+ if (new_page)
+ free_pages_nolock(new_page, 0);
vmf->page = page;
return 0;
-out_sigsegv_memcg:
+
+out_err_locked_memcg:
bpf_map_memcg_exit(old_memcg, new_memcg);
-out_sigsegv:
+out_err_locked:
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
- return VM_FAULT_SIGSEGV;
+ if (new_page)
+ free_pages_nolock(new_page, 0);
+ return fault_ret;
}
static const struct vm_operations_struct arena_vm_ops = {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6874ba1424af0..f9b81638e537a 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -602,15 +602,14 @@ static bool can_alloc_pages(void)
!IS_ENABLED(CONFIG_PREEMPT_RT);
}
+#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)
+
static struct page *__bpf_alloc_page(int nid)
{
if (!can_alloc_pages())
return alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);
- return alloc_pages_node(nid,
- GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT
- | __GFP_NOWARN,
- 0);
+ return alloc_pages_node(nid, BPF_PAGE_GFP, 0);
}
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
@@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
return ret;
}
+/*
+ * For callers that know they run in a sleepable context, e.g. a user page
+ * fault handler. can_alloc_pages() is a conservative guess made for BPF
+ * program context - notably it is always false on PREEMPT_RT - so going
+ * through bpf_map_alloc_pages() there would needlessly pick the
+ * non-blocking allocator, which never reclaims and never engages the OOM
+ * machinery.
+ */
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)
+{
+ might_sleep();
+ return alloc_pages_node(map->numa_node, BPF_PAGE_GFP, 0);
+}
+
static int btf_field_cmp(const void *a, const void *b)
{
const struct btf_field *f1 = a, *f2 = b;
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.c b/tools/testing/selftests/bpf/cgroup_helpers.c
index 45cd0b479fe35..4183ff6150c28 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.c
+++ b/tools/testing/selftests/bpf/cgroup_helpers.c
@@ -188,6 +188,73 @@ int write_cgroup_file_parent(const char *relative_path, const char *file,
return __write_cgroup_file(cgroup_path, file, buf);
}
+static int __read_cgroup_file(const char *cgroup_path, const char *file,
+ char *buf, size_t len)
+{
+ char file_path[PATH_MAX + 1];
+ ssize_t got;
+ int fd;
+
+ snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file);
+ fd = open(file_path, O_RDONLY);
+ if (fd < 0) {
+ log_err("Opening %s", file_path);
+ return 1;
+ }
+
+ got = read(fd, buf, len - 1);
+ if (got < 0) {
+ log_err("Reading %s", file_path);
+ close(fd);
+ return 1;
+ }
+ buf[got] = '\0';
+ close(fd);
+ return 0;
+}
+
+/**
+ * read_cgroup_file() - Read from a cgroup file
+ * @relative_path: The cgroup path, relative to the workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
+/**
+ * read_cgroup_file_parent() - Read from a cgroup file in the parent process
+ * workdir
+ * @relative_path: The cgroup path, relative to the parent process workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory under the parent process
+ * workdir.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_parent_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
/**
* setup_cgroup_environment() - Setup the cgroup environment
*
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.h b/tools/testing/selftests/bpf/cgroup_helpers.h
index 3857304be8741..d42d2e13044e5 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.h
+++ b/tools/testing/selftests/bpf/cgroup_helpers.h
@@ -15,6 +15,10 @@ int write_cgroup_file(const char *relative_path, const char *file,
const char *buf);
int write_cgroup_file_parent(const char *relative_path, const char *file,
const char *buf);
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len);
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len);
int cgroup_setup_and_join(const char *relative_path);
int get_root_cgroup(void);
int create_and_get_cgroup(const char *relative_path);
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_memcg.c b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
new file mode 100644
index 0000000000000..c57b98494cfab
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/user.h>
+#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */
+#include <unistd.h>
+#define PAGE_SIZE getpagesize()
+#endif
+
+#include "cgroup_helpers.h"
+#include "arena_memcg.skel.h"
+
+#define CG_PATH "/arena_memcg"
+
+/* Budget the arena gets on top of whatever is already charged after load. */
+#define ARENA_BUDGET (64 * 1024 * 1024)
+
+static void dump_memcg(int (*rd)(const char *, const char *, char *, size_t))
+{
+ char buf[512];
+
+ /*
+ * memory.current reads 0 once the child has left the cgroup, so it only
+ * carries information when dumped from the live child; memory.peak and
+ * memory.events survive the child and tell the story either way.
+ */
+ if (!rd(CG_PATH, "memory.current", buf, sizeof(buf)))
+ fprintf(stderr, "memory.current: %s", buf);
+ if (!rd(CG_PATH, "memory.max", buf, sizeof(buf)))
+ fprintf(stderr, "memory.max: %s", buf);
+ if (!rd(CG_PATH, "memory.peak", buf, sizeof(buf)))
+ fprintf(stderr, "memory.peak: %s", buf);
+ if (!rd(CG_PATH, "memory.events", buf, sizeof(buf)))
+ fprintf(stderr, "memory.events:\n%s", buf);
+ fflush(stderr);
+}
+
+/* Read one key from a flat keyed cgroup file, e.g. "oom_kill" in memory.events. */
+static long cg_read_key(const char *cg, const char *file, const char *key)
+{
+ char buf[512], *p;
+
+ if (read_cgroup_file(cg, file, buf, sizeof(buf)))
+ return -1;
+ p = strstr(buf, key);
+ if (!p)
+ return -1;
+ return strtol(p + strlen(key), NULL, 10);
+}
+
+void serial_test_arena_memcg(void)
+{
+ int cgroup_fd = -1, status, err;
+ const long ps = PAGE_SIZE;
+ char buf[64];
+ pid_t pid;
+
+ err = setup_cgroup_environment();
+ if (!ASSERT_OK(err, "setup_cgroup_environment"))
+ return;
+
+ cgroup_fd = create_and_get_cgroup(CG_PATH);
+ if (!ASSERT_OK_FD(cgroup_fd, "create_and_get_cgroup"))
+ goto out;
+
+ /* No memory controller -> nothing to test. */
+ if (read_cgroup_file(CG_PATH, "memory.current", buf, sizeof(buf))) {
+ fprintf(stderr, "%s:SKIP:no memory controller\n", __func__);
+ test__skip();
+ goto out;
+ }
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto out;
+ if (pid == 0) {
+ struct arena_memcg *cskel;
+ __u32 i, npages;
+ char *base;
+ size_t sz;
+ long cur;
+
+ /*
+ * Do everything from the child: the arena vma is VM_DONTCOPY so
+ * it would not survive fork(), only the child should be under the
+ * limit so that a memcg OOM cannot pick test_progs, and a map is
+ * charged to the memcg of the task that creates it - so join
+ * before load. The cgroup work dir belongs to the parent that set
+ * the environment up, so reach it with the _parent() helpers.
+ * Errors are reported to the parent through the exit code, since
+ * ASSERT_* in a forked child does not reach it.
+ */
+ snprintf(buf, sizeof(buf), "%d", getpid());
+ if (write_cgroup_file_parent(CG_PATH, "cgroup.procs", buf))
+ _exit(2);
+
+ cskel = arena_memcg__open_and_load();
+ if (!cskel)
+ _exit(3);
+
+ base = bpf_map__initial_value(cskel->maps.arena, &sz);
+ if (!base)
+ _exit(4);
+ npages = bpf_map__max_entries(cskel->maps.arena);
+
+ /*
+ * Cap only now, after load: everything but the fault-in is
+ * charged, so the arena gets a fixed budget regardless of what
+ * the load itself cost, and the load can never hit the limit.
+ */
+ if (read_cgroup_file_parent(CG_PATH, "memory.current", buf, sizeof(buf)))
+ _exit(5);
+ cur = strtol(buf, NULL, 10);
+ snprintf(buf, sizeof(buf), "%ld", cur + ARENA_BUDGET);
+ if (write_cgroup_file_parent(CG_PATH, "memory.max", buf))
+ _exit(6);
+
+ for (i = 0; i < npages; i++)
+ base[(size_t)i * ps] = 1;
+ /* Faulted everything without dying: dump why (only under -v). */
+ dump_memcg(read_cgroup_file_parent);
+ _exit(0);
+ }
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto out;
+
+ /* A non-zero exit means the child failed to set up; the code says where. */
+ if (WIFEXITED(status) && WEXITSTATUS(status)) {
+ ASSERT_OK(WEXITSTATUS(status), "child setup");
+ goto out;
+ }
+
+ /*
+ * Faulting a valid arena address until memory.max is hit must not look
+ * like an invalid access. Without the fix the fault path allocated with
+ * the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,
+ * and the child died with SIGSEGV on a valid address; now it is handled
+ * by the memcg OOM path instead. A SIGKILL alone would not prove the
+ * memcg OOM killer did it (a global OOM or an unrelated crash could also
+ * kill the child), so check memory.events.oom_kill, which records the
+ * memcg OOM and survives the child.
+ */
+ if (!ASSERT_TRUE(WIFSIGNALED(status), "child killed by signal"))
+ goto out;
+ if (!ASSERT_GE(cg_read_key(CG_PATH, "memory.events", "oom_kill"), 1,
+ "memcg oom_kill"))
+ dump_memcg(read_cgroup_file);
+out:
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ cleanup_cgroup_environment();
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c
new file mode 100644
index 0000000000000..88259cfea06b6
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_memcg.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#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, 50000); /* 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");
+
+SEC("syscall")
+int noop(void *ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
|
| 23/3 | 2026/08/25 09:46 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8219 output: 213 thoughts: 1127 Thoughts: **KMSAN Fuzzing Session Assessment: BPF Arena Sleepable Allocator** Okay, let's break this down. We have a patch series touching `kernel/bpf/arena.c` and `kernel/bpf/syscall.c`, aiming to introduce a sleepable page allocator (`bpf_map_alloc_page_sleepable`) for BPF arena page faults. There's also a new selftest for BPF arena memcg limits added. The key question is: does this introduce any uninitialized memory vulnerabilities that warrant a dedicated KMSAN session? First, looking at `kernel/bpf/syscall.c`, `bpf_map_alloc_page_sleepable` is introduced. It internally calls `alloc_pages_node`, but crucially, it uses `BPF_PAGE_GFP`, which *includes* `__GFP_ZERO`. This means the newly allocated page *will* be zeroed. Always a good sign. Now, into `kernel/bpf/arena.c`. `arena_vm_fault` is modified to use this new sleepable allocator. The relevant code preallocates a page outside the spinlock using `bpf_map_alloc_page_sleepable`. If the preallocated page isn't needed, it's freed. If it *is* used, it's mapped. Again, since `__GFP_ZERO` is in the flags, the page is guaranteed to be initialized to zero before mapping. We're explicitly initializing the memory, so no immediate red flags. The presence of new selftests in `tools/testing/selftests/bpf/cgroup_helpers.c`, `tools/testing/selftests/bpf/prog_tests/arena_memcg.c`, and `tools/testing/selftests/bpf/progs/arena_memcg.c` is good, but doesn't sway our decision on this KMSAN assessment. Okay, let's verify again. I'm going to double check `bpf_map_alloc_page_sleepable`, which explicitly calls `alloc_pages_node` with `BPF_PAGE_GFP`, and *that* includes `__GFP_ZERO`. I'll do the same for `arena_vm_fault`, and I see the exact same thing; that a page allocated by this function is freed if it is not used, and it is a zeroed page when it is mapped. So, even if the page is mapped, it's starting from a known state. Therefore, since the newly allocated pages are zeroed, and there are no changes that introduce new fields to structures copied to userspace without initialization, the logic is related to memory allocation and memcg accounting. The potential issues here are related to locking, memory leaks, or invalid memory accesses and are well covered by KASAN, LOCKDEP and other standard kernel bug detectors. Therefore, based on my analysis of the code, I conclude that `NeedsKMSAN` should be `false`. A dedicated KMSAN fuzzing session isn't justified here. The patch focuses on a context switch from a non-sleepable to a sleepable memory allocator, while maintaining a safe initial memory state and is well-covered by our existing fuzzing and testing infrastructure. I'm comfortable with this assessment. |
| 24/3 | 2026/08/25 09:46 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch modifies the BPF arena page fault handler (`arena_vm_fault`) to preallocate pages using a sleepable allocator (`bpf_map_alloc_page_sleepable`) outside of a spinlock. This allows the memcg OOM killer to run if necessary. The new allocator explicitly uses the `__GFP_ZERO` flag (via `BPF_PAGE_GFP`), ensuring that all newly allocated pages are fully zero-initialized before being mapped into the arena. The patch does not introduce any new structures copied to userspace, nor does it alter control flow in a way that could expose uninitialized stack or heap memory. Any potential bugs introduced by these changes (e.g., memory leaks, use-after-free, or locking issues) would be effectively caught by KASAN, LOCKDEP, and standard kernel bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch modifies the BPF arena page fault handler (`arena_vm_fault`) to preallocate pages using a sleepable allocator (`bpf_map_alloc_page_sleepable`) outside of a spinlock. This allows the memcg OOM killer to run if necessary. The new allocator explicitly uses the `__GFP_ZERO` flag (via `BPF_PAGE_GFP`), ensuring that all newly allocated pages are fully zero-initialized before being mapped into the arena. The patch does not introduce any new structures copied to userspace, nor does it alter control flow in a way that could expose uninitialized stack or heap memory. Any potential bugs introduced by these changes (e.g., memory leaks, use-after-free, or locking issues) would be effectively caught by KASAN, LOCKDEP, and standard kernel bug detectors. Therefore, a dedicated KMSAN fuzzing session is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|