AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/25 11:38 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "arena_vm_fault",
    "arena_alloc_pages",
    "arena_free_pages",
    "arena_free_worker",
    "range_tree_set_avail",
    "range_tree_set_unavail",
    "range_tree_remove_unavail",
    "range_tree_make_avail",
    "range_tree_clear",
    "bpf_arena_alloc_pages",
    "bpf_arena_free_pages"
  ],
  "KMSANReasoning": "The patch addresses race conditions and synchronization issues in the BPF arena subsystem during page faulting, reallocation, and freeing (ensuring ranges remain marked unavailable until TLB flush, PTE unmapping, and page freeing are complete).\n\nThe changes introduce state tracking in `struct arena_free_span` and an `available` flag in `struct range_node`, alongside managing transient unavailable ranges in the arena range tree (`arena-\u003ert`). \n\nFrom a memory sanitization perspective:\n1. No uninitialized memory is read or exposed: All newly added and modified heap allocations (`struct arena_free_span` and `struct range_node`) have all of their fields explicitly initialized immediately after allocation before any read access or insertion into data structures.\n2. No kernel data structures, buffers, or uninitialized padding are copied to userspace (no `copy_to_user`, `put_user`, socket options, etc.).\n3. The potential risks associated with these concurrency and synchronization modifications involve race conditions, use-after-free, double-free, and lock contention/deadlocks. These bug categories are detected by KASAN, LOCKDEP, and MM sanity checks in standard KASAN kernel builds.\n\nSince there are no uninitialized memory read risks or info-leaks, spawning a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch refactors BPF arena memory management and range tree handling to prevent races between concurrent arena page allocation/faulting and freeing/TLB flushing. It modifies arena page fault handling (arena_vm_fault), freeing workflows (arena_free_pages, arena_free_worker), and introduces range node availability tracking in the range tree, along with multiple new WARN_ON_ONCE assertions. This code is reachable from user space on amd64 via BPF system calls (creating BPF_MAP_TYPE_ARENA maps), mmap(), page faults, and arena kfuncs.",
  "WorthFuzzing": true
}

1/1 2026/09/25 11:38 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 9a0e7021e2025ed16d20a20f6bcbe33bb7e16186\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Sep 25 11:38:45 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c\nindex 7b6847200b431..299525f93bbda 100644\n--- a/kernel/bpf/arena.c\n+++ b/kernel/bpf/arena.c\n@@ -72,10 +72,18 @@ struct bpf_arena {\n static void arena_free_worker(struct work_struct *work);\n static void arena_free_irq(struct irq_work *iw);\n \n+enum arena_free_span_state {\n+\tARENA_FREE_SPAN_NOT_STARTED,\t/* Freeing not started */\n+\tARENA_FREE_SPAN_UNAVAIL,\t/* Region cleared \u0026 unavailable */\n+\tARENA_FREE_SPAN_ZAPPED,\t\t/* Region zapped and cleared */\n+\tARENA_FREE_SPAN_FAILED,\t\t/* Freeing operation cannot continue */\n+};\n+\n struct arena_free_span {\n \tstruct llist_node node;\n \tunsigned long uaddr;\n \tu32 page_cnt;\n+\tenum arena_free_span_state state;\n };\n \n u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\n@@ -317,7 +325,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)\n \t\tgoto err_free_arena;\n \n \trange_tree_init(\u0026arena-\u003ert);\n-\terr = range_tree_set(\u0026arena-\u003ert, 0, attr-\u003emax_entries);\n+\terr = range_tree_set_avail(\u0026arena-\u003ert, 0, attr-\u003emax_entries);\n \tif (err)\n \t\tgoto err_free_scratch;\n \tmutex_init(\u0026arena-\u003elock);\n@@ -481,21 +489,22 @@ 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 range_node *unavail_node;\n \tstruct page *page;\n \tlong kbase, kaddr;\n \tunsigned long flags;\n+\tvm_fault_t ret_fault;\n \tint ret;\n \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-\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+\tret = raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags);\n+\t/* If we are deadlocking somehow, no way to ensure forward progress. */\n+\tif (ret == -EDEADLK)\n \t\treturn VM_FAULT_SIGBUS;\n+\tif (ret)\n+\t\tgoto retry;\n \n \tpage = vmalloc_to_page((void *)kaddr);\n \tif (page) {\n@@ -513,6 +522,14 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n \t\tgoto out_sigsegv_memcg;\n \n \tret = range_tree_clear(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n+\t/* If a range is unavailable, try again. */\n+\tif (ret == -EAGAIN) {\n+\t\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\t\tbpf_map_memcg_exit(old_memcg, new_memcg);\n+\n+\t\tgoto retry;\n+\t}\n+\n \tif (ret)\n \t\tgoto out_sigsegv_memcg;\n \n@@ -520,28 +537,61 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\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\trange_tree_set_avail(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n \t\tgoto out_sigsegv_memcg;\n \t}\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\trange_tree_set_avail(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n \t\tfree_pages_nolock(page, 0);\n \t\tgoto out_sigsegv_memcg;\n \t}\n \tflush_vmap_cache(kaddr, PAGE_SIZE);\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n out:\n-\tpage_ref_add(page, 1);\n+\t/* Reserve the page while installing its user PTE without the arena lock. */\n+\tbpf_map_memcg_enter(\u0026arena-\u003emap, \u0026old_memcg, \u0026new_memcg);\n+\tunavail_node = range_tree_set_unavail(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n+\tbpf_map_memcg_exit(old_memcg, new_memcg);\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n-\tvmf-\u003epage = page;\n-\treturn 0;\n+\tif (IS_ERR(unavail_node)) {\n+\t\tret = PTR_ERR(unavail_node);\n+\t\tif (ret == -EAGAIN)\n+\t\t\tgoto retry;\n+\t\treturn VM_FAULT_OOM;\n+\t}\n+\n+\tret_fault = vmf_insert_page(vmf-\u003evma, vmf-\u003eaddress, page);\n+\n+\twhile ((ret = raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags))) {\n+\t\t/* If we somehow deadlocked stop trying to take the lock. */\n+\t\tif (ret == -EDEADLK)\n+\t\t\treturn VM_FAULT_SIGBUS;\n+\n+\t\tcond_resched();\n+\t}\n+\n+\tret = range_tree_remove_unavail(\u0026arena-\u003ert, vmf-\u003epgoff, 1);\n+\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\tWARN_ON_ONCE(ret);\n+\treturn ret_fault;\n out_sigsegv_memcg:\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n out_sigsegv:\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n \treturn VM_FAULT_SIGSEGV;\n+\n+retry:\n+\n+\t/* Only for special cases (GUP/device drivers). */\n+\tif (!(vmf-\u003eflags \u0026 FAULT_FLAG_ALLOW_RETRY))\n+\t\treturn VM_FAULT_SIGBUS;\n+\n+\tif (!(vmf-\u003eflags \u0026 FAULT_FLAG_RETRY_NOWAIT))\n+\t\trelease_fault_lock(vmf);\n+\n+\treturn VM_FAULT_RETRY;\n }\n \n static const struct vm_operations_struct arena_vm_ops = {\n@@ -621,7 +671,7 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\n \t * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from\n \t * being copied into the child process on fork.\n \t */\n-\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);\n+\tvm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY | VM_MIXEDMAP);\n \tvma-\u003evm_ops = \u0026arena_vm_ops;\n \treturn 0;\n }\n@@ -766,7 +816,7 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n \treturn clear_lo32(arena-\u003euser_vm_start) + uaddr32;\n out:\n-\trange_tree_set(\u0026arena-\u003ert, pgoff + mapped, page_cnt - mapped);\n+\trange_tree_set_avail(\u0026arena-\u003ert, pgoff + mapped, page_cnt - mapped);\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n \tif (mapped) {\n \t\tflush_vmap_cache(kern_vm_start + uaddr32, mapped \u003c\u003c PAGE_SHIFT);\n@@ -847,6 +897,7 @@ static void zap_pages(struct bpf_arena *arena, long uaddr, long page_cnt)\n static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)\n {\n \tstruct mem_cgroup *new_memcg, *old_memcg;\n+\tstruct range_node *unavail_node = NULL;\n \tu64 full_uaddr, uaddr_end;\n \tlong kaddr, pgoff;\n \tstruct page *page;\n@@ -855,6 +906,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,\n \tstruct arena_free_span *s;\n \tstruct clear_range_data cdata;\n \tunsigned long flags;\n+\tbool release_only = false;\n \tint ret = 0;\n \n \t/* only aligned lower 32-bit are relevant */\n@@ -881,7 +933,20 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,\n \tif (ret)\n \t\tgoto defer;\n \n-\trange_tree_set(\u0026arena-\u003ert, pgoff, page_cnt);\n+\tunavail_node = range_tree_set_unavail(\u0026arena-\u003ert, pgoff, page_cnt);\n+\tif (IS_ERR(unavail_node)) {\n+\t\tret = PTR_ERR(unavail_node);\n+\t\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\t\t/*\n+\t\t * For -EAGAIN: An overlapping fault reserves\n+\t\t * the range before installing its PTE.\n+\t\t */\n+\t\tif (ret == -ENOMEM || ret == -EAGAIN)\n+\t\t\tgoto defer;\n+\t\tWARN_ON_ONCE(ret);\n+\t\tbpf_map_memcg_exit(old_memcg, new_memcg);\n+\t\treturn;\n+\t}\n \n \tinit_llist_head(\u0026free_pages);\n \tcdata.arena = arena;\n@@ -911,6 +976,16 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,\n \t\t\tzap_pages(arena, full_uaddr, 1);\n \t\t__free_page(page);\n \t}\n+\n+\tret = raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags);\n+\tif (ret) {\n+\t\trelease_only = true;\n+\t\tgoto defer;\n+\t}\n+\n+\tret = range_tree_make_avail(\u0026arena-\u003ert, pgoff, page_cnt);\n+\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\tWARN_ON_ONCE(ret);\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n \n \treturn;\n@@ -918,16 +993,33 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,\n defer:\n \ts = kmalloc_nolock(sizeof(struct arena_free_span), __GFP_ACCOUNT, -1);\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n-\tif (!s)\n+\tif (!s) {\n \t\t/*\n-\t\t * If allocation fails in non-sleepable context, pages are intentionally left\n-\t\t * inaccessible (leaked) until the arena is destroyed. Cleanup or retries are not\n-\t\t * possible here, so we intentionally omit them for safety.\n+\t\t * Directly mark the region available. Unavailable regions must\n+\t\t * always be transient, and a permanent one breaks the assumptions\n+\t\t * made in the allocation/faulting code. The operation is safe because\n+\t\t * unavailable nodes are not modified by the range tree code without a reference\n+\t\t * to the node. The modification from unavailable to available also does\n+\t\t * not require touching anything in the tree apart from the node itself,\n+\t\t * so it can be done locklessly. The downside is fragmentation in the tree,\n+\t\t * since we cannot merge with neighbors, but this is a) safe and b) better\n+\t\t * than leaking the range.\n \t\t */\n+\t\tif (release_only)\n+\t\t\trange_node_mark_available(unavail_node);\n+\n+\t\t/*\n+\t\t * If we haven't even zapped the pages, intentionally leave them\n+\t\t * inaccessible (leaked) until the arena is destroyed. Cleanup or\n+\t\t * retries are not possible here, so we intentionally omit them for safety.\n+\t\t */\n+\n \t\treturn;\n+\t}\n \n \ts-\u003epage_cnt = page_cnt;\n \ts-\u003euaddr = uaddr;\n+\ts-\u003estate = release_only ? ARENA_FREE_SPAN_ZAPPED : ARENA_FREE_SPAN_NOT_STARTED;\n \tllist_add(\u0026s-\u003enode, \u0026arena-\u003efree_spans);\n \tirq_work_queue(\u0026arena-\u003efree_irq);\n }\n@@ -976,6 +1068,7 @@ static void arena_free_worker(struct work_struct *work)\n \tstruct mem_cgroup *new_memcg, *old_memcg;\n \tstruct llist_node *list, *pos, *t;\n \tstruct arena_free_span *s;\n+\tstruct range_node *unavail_node;\n \tu64 arena_vm_start, user_vm_start;\n \tstruct llist_head free_pages;\n \tstruct clear_range_data cdata;\n@@ -983,6 +1076,8 @@ static void arena_free_worker(struct work_struct *work)\n \tunsigned long full_uaddr;\n \tlong kaddr, page_cnt, pgoff;\n \tunsigned long flags;\n+\tbool retry = false;\n+\tint ret;\n \n \tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags)) {\n \t\tschedule_work(work);\n@@ -998,23 +1093,48 @@ static void arena_free_worker(struct work_struct *work)\n \tuser_vm_start = bpf_arena_get_user_vm_start(arena);\n \n \tlist = llist_del_all(\u0026arena-\u003efree_spans);\n-\tllist_for_each(pos, list) {\n+\tllist_for_each_safe(pos, t, list) {\n \t\ts = llist_entry(pos, struct arena_free_span, node);\n+\t\tif (s-\u003estate != ARENA_FREE_SPAN_NOT_STARTED)\n+\t\t\tcontinue;\n+\n \t\tpage_cnt = s-\u003epage_cnt;\n-\t\tkaddr = arena_vm_start + s-\u003euaddr;\n \t\tpgoff = compute_pgoff(arena, s-\u003euaddr);\n+\t\tkaddr = arena_vm_start + s-\u003euaddr;\n+\n+\t\tunavail_node = range_tree_set_unavail(\u0026arena-\u003ert, pgoff, page_cnt);\n+\t\tif (IS_ERR(unavail_node)) {\n+\t\t\tret = PTR_ERR(unavail_node);\n+\t\t\t/* Kick off another attempt at the end of this call. */\n+\t\t\tif (ret == -EAGAIN)\n+\t\t\t\tcontinue;\n+\n+\t\t\t/*\n+\t\t\t * An -ENOMEM failure is the same failure mode as in\n+\t\t\t * the defer: path of arena_free_pages(). Do not treat\n+\t\t\t * the leak as a bug.\n+\t\t\t */\n+\t\t\tif (ret != -ENOMEM)\n+\t\t\t\tWARN_ON_ONCE(ret);\n+\n+\t\t\ts-\u003estate = ARENA_FREE_SPAN_FAILED;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\ts-\u003estate = ARENA_FREE_SPAN_UNAVAIL;\n \n \t\t/* clear ptes and collect pages in free_pages llist */\n \t\tapply_to_existing_page_range(\u0026init_mm, kaddr, page_cnt \u003c\u003c PAGE_SHIFT,\n \t\t\t\t\t     apply_range_clear_cb, \u0026cdata);\n-\n-\t\trange_tree_set(\u0026arena-\u003ert, pgoff, page_cnt);\n \t}\n \traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n \n-\t/* Iterate the list again without holding spinlock to do the tlb flush and zap_pages */\n+\t/* Keep ranges unavailable until their stale translations are gone. */\n \tllist_for_each_safe(pos, t, list) {\n \t\ts = llist_entry(pos, struct arena_free_span, node);\n+\t\tif (s-\u003estate != ARENA_FREE_SPAN_UNAVAIL)\n+\t\t\tcontinue;\n+\n \t\tpage_cnt = s-\u003epage_cnt;\n \t\tfull_uaddr = clear_lo32(user_vm_start) + s-\u003euaddr;\n \t\tkaddr = arena_vm_start + s-\u003euaddr;\n@@ -1025,7 +1145,11 @@ static void arena_free_worker(struct work_struct *work)\n \t\t/* remove pages from user vmas */\n \t\tzap_pages(arena, full_uaddr, page_cnt);\n \n-\t\tkfree_nolock(s);\n+\t\t/*\n+\t\t * Used to avoid zapping twice if we fail the lock acquisition\n+\t\t * below and rerun the span through this function.\n+\t\t */\n+\t\ts-\u003estate = ARENA_FREE_SPAN_ZAPPED;\n \t}\n \n \t/* free all pages collected by apply_to_existing_page_range() in the first loop */\n@@ -1034,7 +1158,56 @@ static void arena_free_worker(struct work_struct *work)\n \t\t__free_page(page);\n \t}\n \n+\tif (raw_res_spin_lock_irqsave(\u0026arena-\u003espinlock, flags)) {\n+\t\tllist_for_each_safe(pos, t, list) {\n+\t\t\ts = llist_entry(pos, struct arena_free_span, node);\n+\n+\t\t\tif (s-\u003estate == ARENA_FREE_SPAN_FAILED) {\n+\t\t\t\tkfree_nolock(s);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tllist_add(pos, \u0026arena-\u003efree_spans);\n+\t\t\tretry = true;\n+\t\t}\n+\t\tgoto done;\n+\t}\n+\n+\tllist_for_each_safe(pos, t, list) {\n+\t\ts = llist_entry(pos, struct arena_free_span, node);\n+\n+\t\t/* Remove the spans of failed allocations. */\n+\t\tif (s-\u003estate == ARENA_FREE_SPAN_FAILED) {\n+\t\t\tkfree_nolock(s);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (s-\u003estate == ARENA_FREE_SPAN_NOT_STARTED) {\n+\t\t\tllist_add(pos, \u0026arena-\u003efree_spans);\n+\t\t\tretry = true;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tpage_cnt = s-\u003epage_cnt;\n+\t\tpgoff = compute_pgoff(arena, s-\u003euaddr);\n+\t\t/*\n+\t\t * This range tree operation does not allocate memory,\n+\t\t * and so should never fail regardless of contention\n+\t\t * or memory pressure. This is in contrast to regular\n+\t\t * inserts that _can_ fail under memory pressure and\n+\t\t * force us to defer the free.\n+\t\t */\n+\t\tret = range_tree_make_avail(\u0026arena-\u003ert, pgoff, page_cnt);\n+\t\tWARN_ON_ONCE(ret);\n+\t\tkfree_nolock(s);\n+\t}\n+\traw_res_spin_unlock_irqrestore(\u0026arena-\u003espinlock, flags);\n+\n+done:\n \tbpf_map_memcg_exit(old_memcg, new_memcg);\n+\n+\tif (retry)\n+\t\tschedule_work(work);\n }\n \n static void arena_free_irq(struct irq_work *iw)\ndiff --git a/kernel/bpf/range_tree.c b/kernel/bpf/range_tree.c\nindex 2f28886f3ff75..62ebf4df51cb4 100644\n--- a/kernel/bpf/range_tree.c\n+++ b/kernel/bpf/range_tree.c\n@@ -3,6 +3,7 @@\n #include \u003clinux/interval_tree_generic.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/bpf.h\u003e\n+#include \u003clinux/err.h\u003e\n #include \"range_tree.h\"\n \n /*\n@@ -39,8 +40,29 @@ struct range_node {\n \tu32 rn_start;\n \tu32 rn_last; /* inclusive */\n \tu32 __rn_subtree_last;\n+\tbool available; /* range is available for allocating. */\n };\n \n+/* Is the range available for merging? */\n+static inline bool range_available(struct range_node *rn)\n+{\n+\t/* Pairs with smp_store_release() in range_node_mark_available(). */\n+\treturn rn \u0026\u0026 smp_load_acquire(\u0026rn-\u003eavailable);\n+}\n+\n+/*\n+ * Mark a node as available. Designed to be used locklessly\n+ * as a last-ditch effort to avoid leaking unavailable range\n+ * tree nodes when all attempts to directly or indirectly\n+ * free an arena region has failed. See arena_free_pages()\n+ * for more info.\n+ */\n+void range_node_mark_available(struct range_node *rn)\n+{\n+\t/* Pairs with smp_load_acquire() in range_available(). */\n+\tsmp_store_release(\u0026rn-\u003eavailable, true);\n+}\n+\n static struct range_node *rb_to_range_node(struct rb_node *rb)\n {\n \treturn rb_entry(rb, struct range_node, rb_range_size);\n@@ -55,20 +77,30 @@ static u32 rn_size(struct range_node *rn)\n static inline struct range_node *__find_range(struct range_tree *rt, u32 len)\n {\n \tstruct rb_node *rb = rt-\u003erange_size_root.rb_root.rb_node;\n-\tstruct range_node *best = NULL;\n+\tstruct rb_node *best = NULL;\n+\tstruct range_node *rn;\n \n \twhile (rb) {\n-\t\tstruct range_node *rn = rb_to_range_node(rb);\n+\t\trn = rb_to_range_node(rb);\n \n \t\tif (len \u003c= rn_size(rn)) {\n-\t\t\tbest = rn;\n+\t\t\tbest = rb;\n \t\t\trb = rb-\u003erb_right;\n \t\t} else {\n \t\t\trb = rb-\u003erb_left;\n \t\t}\n \t}\n \n-\treturn best;\n+\t/* Filter unavailable ranges. */\n+\twhile (best) {\n+\t\trn = rb_to_range_node(best);\n+\t\tif (range_available(rn))\n+\t\t\treturn rn;\n+\n+\t\tbest = rb_prev(best);\n+\t}\n+\n+\treturn NULL;\n }\n \n s64 range_tree_find(struct range_tree *rt, u32 len)\n@@ -135,10 +167,19 @@ range_it_iter_first(struct range_tree *rt, u32 start, u32 last)\n /* Clear the range in this range tree */\n int range_tree_clear(struct range_tree *rt, u32 start, u32 len)\n {\n+\tu32 first = start;\n \tu32 last = start + len - 1;\n \tstruct range_node *new_rn;\n \tstruct range_node *rn;\n \n+\t/* Scan for unavailable ranges and try again if so. */\n+\twhile ((rn = range_it_iter_first(rt, first, last))) {\n+\t\tif (!range_available(rn))\n+\t\t\treturn -EAGAIN;\n+\n+\t\tfirst = rn-\u003ern_last + 1;\n+\t}\n+\n \twhile ((rn = range_it_iter_first(rt, start, last))) {\n \t\tif (rn-\u003ern_start \u003c start \u0026\u0026 rn-\u003ern_last \u003e last) {\n \t\t\tu32 old_last = rn-\u003ern_last;\n@@ -153,6 +194,7 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)\n \t\t\t\t\t\tNUMA_NO_NODE);\n \t\t\tif (!new_rn)\n \t\t\t\treturn -ENOMEM;\n+\t\t\tnew_rn-\u003eavailable = rn-\u003eavailable;\n \t\t\tnew_rn-\u003ern_start = last + 1;\n \t\t\tnew_rn-\u003ern_last = old_last;\n \t\t\trange_it_insert(new_rn, rt);\n@@ -180,32 +222,31 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)\n int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)\n {\n \tu32 last = start + len - 1;\n-\tstruct range_node *left;\n+\tstruct range_node *rn;\n \n-\t/* Is this whole range set ? */\n-\tleft = range_it_iter_first(rt, start, last);\n-\tif (left \u0026\u0026 left-\u003ern_start \u003c= start \u0026\u0026 left-\u003ern_last \u003e= last)\n-\t\treturn 0;\n+\tfor ((rn = range_it_iter_first(rt, start, last)); rn != NULL;\n+\t\trn = __range_it_iter_next(rn, start, last)) {\n+\t\t/* Make sure the range covers the start */\n+\t\tif (rn-\u003ern_start \u003e start)\n+\t\t\treturn -ESRCH;\n+\n+\t\t/* If it covers the entire range we're done. */\n+\t\tif (rn-\u003ern_last \u003e= last)\n+\t\t\treturn 0;\n+\n+\t\tstart = rn-\u003ern_last + 1;\n+\t}\n+\n+\t/* No range to cover [start, last] */\n \treturn -ESRCH;\n }\n \n-/* Set the range in this range tree */\n-int range_tree_set(struct range_tree *rt, u32 start, u32 len)\n+/* Do we have adjacent ranges (and do not overlap with them)? */\n+static int range_get_adjacent(struct range_tree *rt, u32 start, u32 last,\n+\t\tstruct range_node **leftp, struct range_node **rightp)\n {\n-\tu32 last = start + len - 1;\n \tstruct range_node *right;\n \tstruct range_node *left;\n-\tint err;\n-\n-\t/* Is this whole range already set ? */\n-\tleft = range_it_iter_first(rt, start, last);\n-\tif (left \u0026\u0026 left-\u003ern_start \u003c= start \u0026\u0026 left-\u003ern_last \u003e= last)\n-\t\treturn 0;\n-\n-\t/* Clear out everything in the range we want to set. */\n-\terr = range_tree_clear(rt, start, len);\n-\tif (err)\n-\t\treturn err;\n \n \t/* Do we have a left-adjacent range ? */\n \tleft = range_it_iter_first(rt, start - 1, start - 1);\n@@ -217,31 +258,163 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)\n \tif (right \u0026\u0026 right-\u003ern_start != last + 1)\n \t\treturn -EFAULT;\n \n-\tif (left \u0026\u0026 right) {\n+\t*leftp = left;\n+\t*rightp = right;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Merge with adjacent available ranges if possible. The new [start, last]\n+ * has already been confirmed to be adjacent with left/right by the caller.\n+ */\n+static int range_tree_merge(struct range_tree *rt, u32 start, u32 last,\n+\t\tstruct range_node *left, struct range_node *right)\n+{\n+\tif (range_available(left) \u0026\u0026 range_available(right)) {\n \t\t/* Combine left and right adjacent ranges */\n \t\trange_it_remove(left, rt);\n \t\trange_it_remove(right, rt);\n \t\tleft-\u003ern_last = right-\u003ern_last;\n \t\trange_it_insert(left, rt);\n \t\tkfree_nolock(right);\n-\t} else if (left) {\n+\t} else if (range_available(left)) {\n \t\t/* Combine with the left range */\n \t\trange_it_remove(left, rt);\n \t\tleft-\u003ern_last = last;\n \t\trange_it_insert(left, rt);\n-\t} else if (right) {\n+\t} else if (range_available(right)) {\n \t\t/* Combine with the right range */\n \t\trange_it_remove(right, rt);\n \t\tright-\u003ern_start = start;\n \t\trange_it_insert(right, rt);\n \t} else {\n-\t\tleft = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);\n-\t\tif (!left)\n-\t\t\treturn -ENOMEM;\n-\t\tleft-\u003ern_start = start;\n-\t\tleft-\u003ern_last = last;\n-\t\trange_it_insert(left, rt);\n+\t\t/* No merge available. */\n+\t\treturn -ENOENT;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Make a range available, possibly merging. */\n+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len)\n+{\n+\tu32 last = start + len - 1;\n+\tstruct range_node *rn;\n+\tstruct range_node *right;\n+\tstruct range_node *left;\n+\tint err;\n+\n+\t/*\n+\t * Confirm the range exists is unavailable,\n+\t * and fits the requested range exactly.\n+\t */\n+\trn = range_it_iter_first(rt, start, last);\n+\tif (!rn || rn-\u003eavailable)\n+\t\treturn -EINVAL;\n+\n+\tif (rn-\u003ern_start != start || rn-\u003ern_last != last)\n+\t\treturn -EINVAL;\n+\n+\terr = range_get_adjacent(rt, start, last, \u0026left, \u0026right);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/* If no merging required, just make available. */\n+\tif (!range_available(left) \u0026\u0026 !range_available(right)) {\n+\t\trn-\u003eavailable = true;\n+\t\treturn 0;\n \t}\n+\n+\t/* Can merge, remove the range already. */\n+\tstart = rn-\u003ern_start;\n+\tlast = rn-\u003ern_last;\n+\trange_it_remove(rn, rt);\n+\tkfree_nolock(rn);\n+\n+\treturn range_tree_merge(rt, start, last, left, right);\n+}\n+\n+/* Set the range in this range tree */\n+static int range_tree_set(struct range_tree *rt, u32 start, u32 len, bool available,\n+\t\t\t  struct range_node **new_rn)\n+{\n+\tu32 last = start + len - 1;\n+\tstruct range_node *right;\n+\tstruct range_node *left;\n+\tint err;\n+\n+\t/* Is this whole range already set ? */\n+\tleft = range_it_iter_first(rt, start, last);\n+\tif (left \u0026\u0026 left-\u003ern_start \u003c= start \u0026\u0026 left-\u003ern_last \u003e= last \u0026\u0026\n+\t    range_available(left) \u0026\u0026 available)\n+\t\treturn 0;\n+\n+\t/* Clear out everything in the range we want to set. */\n+\terr = range_tree_clear(rt, start, len);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/* Get adjacent ranges and check for overlaps. */\n+\terr = range_get_adjacent(rt, start, last, \u0026left, \u0026right);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/*\n+\t * If the range is not available for allocation, don't merge.\n+\t * Unavailable ranges are in the process of being freed and should\n+\t * be imminently marked available, so merging them with other\n+\t * unavailable ranges will just lead to splitting the range back\n+\t * almost immediately.\n+\t */\n+\tif (available) {\n+\t\terr = range_tree_merge(rt, start, last, left, right);\n+\t\tif (!err)\n+\t\t\treturn 0;\n+\t}\n+\n+\tleft = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);\n+\tif (!left)\n+\t\treturn -ENOMEM;\n+\tleft-\u003eavailable = available;\n+\tleft-\u003ern_start = start;\n+\tleft-\u003ern_last = last;\n+\trange_it_insert(left, rt);\n+\tif (new_rn)\n+\t\t*new_rn = left;\n+\n+\treturn 0;\n+}\n+\n+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len)\n+{\n+\treturn range_tree_set(rt, start, len, true, NULL);\n+}\n+\n+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len)\n+{\n+\tstruct range_node *rn = NULL;\n+\tint err;\n+\n+\terr = range_tree_set(rt, start, len, false, \u0026rn);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\tif (WARN_ON_ONCE(!rn))\n+\t\treturn ERR_PTR(-EINVAL);\n+\treturn rn;\n+}\n+\n+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len)\n+{\n+\tu32 last = start + len - 1;\n+\tstruct range_node *rn;\n+\n+\trn = range_it_iter_first(rt, start, last);\n+\tif (!rn || rn-\u003eavailable || rn-\u003ern_start != start || rn-\u003ern_last != last)\n+\t\treturn -EINVAL;\n+\n+\trange_it_remove(rn, rt);\n+\tkfree_nolock(rn);\n \treturn 0;\n }\n \ndiff --git a/kernel/bpf/range_tree.h b/kernel/bpf/range_tree.h\nindex ff0b9110eb715..79295abe36845 100644\n--- a/kernel/bpf/range_tree.h\n+++ b/kernel/bpf/range_tree.h\n@@ -10,11 +10,17 @@ struct range_tree {\n \tstruct rb_root_cached range_size_root;\n };\n \n+struct range_node;\n+\n void range_tree_init(struct range_tree *rt);\n void range_tree_destroy(struct range_tree *rt);\n \n int range_tree_clear(struct range_tree *rt, u32 start, u32 len);\n-int range_tree_set(struct range_tree *rt, u32 start, u32 len);\n+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len);\n+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len);\n+void range_node_mark_available(struct range_node *rn);\n+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len);\n+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len);\n int is_range_tree_set(struct range_tree *rt, u32 start, u32 len);\n s64 range_tree_find(struct range_tree *rt, u32 len);\n \ndiff --git a/tools/testing/selftests/bpf/prog_tests/arena_race.c b/tools/testing/selftests/bpf/prog_tests/arena_race.c\nnew file mode 100644\nindex 0000000000000..c2da611421f3a\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/arena_race.c\n@@ -0,0 +1,270 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#define _GNU_SOURCE\n+#include \u003csched.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003ctest_progs.h\u003e\n+\n+#include \"arena_race.skel.h\"\n+\n+struct free_thread_ctx {\n+\tstruct arena_race *skel;\n+\tint err;\n+\t__u32 retval;\n+};\n+\n+struct fault_thread_ctx {\n+\t__u64 *addr;\n+\tint stop;\n+};\n+\n+static int run_prog(struct bpf_program *prog, const char *name)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, opts);\n+\tint err;\n+\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(prog), \u0026opts);\n+\treturn ASSERT_OK(err, name) \u0026\u0026 ASSERT_OK(opts.retval, name) ? 0 : -1;\n+}\n+\n+/* Trigger the sleepable free page path. */\n+static void *run_free_thread(void *arg)\n+{\n+\tLIBBPF_OPTS(bpf_test_run_opts, opts);\n+\tstruct free_thread_ctx *ctx = arg;\n+\n+\tctx-\u003eskel-\u003ebss-\u003etarget_tid = sys_gettid();\n+\tctx-\u003eerr = bpf_prog_test_run_opts(\n+\t\tbpf_program__fd(ctx-\u003eskel-\u003eprogs.free_page), \u0026opts);\n+\tctx-\u003eretval = opts.retval;\n+\treturn NULL;\n+}\n+\n+/* Continuously fault in the address. */\n+static void *fault_reader_thread(void *arg)\n+{\n+\tstruct fault_thread_ctx *ctx = arg;\n+\n+\twhile (!READ_ONCE(ctx-\u003estop))\n+\t\t(void)READ_ONCE(*ctx-\u003eaddr);\n+\treturn NULL;\n+}\n+\n+static int wait_for(int *p)\n+{\n+\t__u64 deadline = get_time_ns() + 5ULL * 1000 * 1000 * 1000;\n+\n+\twhile (!READ_ONCE(*p)) {\n+\t\tif (get_time_ns() \u003e deadline)\n+\t\t\treturn -ETIMEDOUT;\n+\t}\n+\treturn 0;\n+}\n+\n+static struct arena_race *setup_arena(__u64 **addr, bool trace_flush)\n+{\n+\tstruct arena_race *skel;\n+\tsize_t arena_sz;\n+\tchar *base;\n+\tint err;\n+\n+\tskel = arena_race__open();\n+\tif (!ASSERT_OK_PTR(skel, \"open\"))\n+\t\treturn NULL;\n+\terr = bpf_program__set_autoload(skel-\u003eprogs.trace_flush, trace_flush);\n+\tif (!ASSERT_OK(err, \"trace_flush_autoload\"))\n+\t\tgoto err_out;\n+\n+\terr = arena_race__load(skel);\n+\tif (!ASSERT_OK(err, \"load\"))\n+\t\tgoto err_out;\n+\terr = arena_race__attach(skel);\n+\tif (!ASSERT_OK(err, \"attach\"))\n+\t\tgoto err_out;\n+\n+\tif (run_prog(skel-\u003eprogs.alloc_old, \"alloc_old\"))\n+\t\tgoto err_out;\n+\tif (skel-\u003ebss-\u003eskip) {\n+\t\ttest__skip();\n+\t\tgoto err_out;\n+\t}\n+\n+\tbase = bpf_map__initial_value(skel-\u003emaps.arena, \u0026arena_sz);\n+\tif (!ASSERT_OK_PTR(base, \"arena_base\"))\n+\t\tgoto err_out;\n+\t*addr = (__u64 *)(base + getpagesize());\n+\tif (!ASSERT_EQ((unsigned long)skel-\u003ebss-\u003eptr, (unsigned long)*addr,\n+\t\t       \"arena_ptr\"))\n+\t\tgoto err_out;\n+\treturn skel;\n+\n+err_out:\n+\tarena_race__destroy(skel);\n+\treturn NULL;\n+}\n+\n+static void test_free_before_flush(bool deferred)\n+{\n+\tstruct free_thread_ctx ctx = {};\n+\tstruct arena_race *skel;\n+\tpthread_t thread;\n+\t__u64 *addr;\n+\tbool thread_created = false, flush_seen = false, completed = false;\n+\tint err;\n+\n+\tif (libbpf_find_vmlinux_btf_id(\"flush_tlb_kernel_range\",\n+\t\t\t\t\tBPF_TRACE_FENTRY) \u003c= 0) {\n+\t\tprintf(\"%s:SKIP: flush_tlb_kernel_range is not an fentry target\\n\",\n+\t\t       __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tskel = setup_arena(\u0026addr, true);\n+\tif (!skel)\n+\t\treturn;\n+\n+\t/* Pause during a TLB flush to widen the race window. */\n+\tskel-\u003ebss-\u003epause_on_flush = 1;\n+\n+\tif (deferred) {\n+\t\t/*\n+\t\t * Test the nonsleepable free path that gets\n+\t\t * deferred to a worker in the kernel. We do\n+\t\t * so by triggering the arena operation from\n+\t\t * a nonsleepable tracepoint context.\n+\t\t */\n+\t\tskel-\u003ebss-\u003etrigger_pid_tgid =\n+\t\t\t((__u64)getpid() \u003c\u003c 32) | (__u32)sys_gettid();\n+\t\tskel-\u003ebss-\u003etrigger_syscall = SYS_getpgid;\n+\t\tskel-\u003ebss-\u003edeferred_free = 1;\n+\t\tif (!ASSERT_GE(syscall(SYS_getpgid, 0), 0, \"deferred_free\"))\n+\t\t\tgoto release;\n+\t} else {\n+\t\t/*\n+\t\t * Test the sleepable arena free path through a\n+\t\t * syscall test prog.\n+\t\t */\n+\t\tctx.skel = skel;\n+\t\terr = pthread_create(\u0026thread, NULL, run_free_thread, \u0026ctx);\n+\t\tif (!ASSERT_OK(err, \"pthread_create\")) {\n+\t\t\tskel-\u003ebss-\u003erelease = 1;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tthread_created = true;\n+\t}\n+\n+\t/* Wait until the worker thread triggers a flush. */\n+\terr = wait_for(\u0026skel-\u003ebss-\u003eflush_entered);\n+\tif (!ASSERT_OK(err, \"flush_entered\"))\n+\t\tgoto release;\n+\n+\tflush_seen = true;\n+\n+\t/* Force a reallocation during the flush. */\n+\trun_prog(skel-\u003eprogs.try_realloc, \"realloc_before_flush\");\n+\tASSERT_NULL(skel-\u003ebss-\u003erealloc_ptr, \"realloc_before_flush\");\n+\n+release:\n+\tskel-\u003ebss-\u003erelease = 1;\n+\tif (thread_created) {\n+\t\tASSERT_OK(pthread_join(thread, NULL), \"pthread_join\");\n+\t\tthread_created = false;\n+\t\tcompleted = ASSERT_OK(ctx.err, \"free_run\") \u0026\u0026\n+\t\t\t    ASSERT_OK(ctx.retval, \"free_retval\");\n+\t} else if (skel-\u003ebss-\u003etarget_tid) {\n+\t\terr = wait_for(\u0026skel-\u003ebss-\u003eworker_exited);\n+\t\tcompleted = ASSERT_OK(err, \"worker_exited\");\n+\t}\n+\tASSERT_FALSE(skel-\u003ebss-\u003etimed_out, \"flush_timed_out\");\n+\n+\tif (flush_seen \u0026\u0026 completed \u0026\u0026\n+\t    !run_prog(skel-\u003eprogs.try_realloc, \"realloc_after_flush\")) {\n+\t\tASSERT_EQ((unsigned long)skel-\u003ebss-\u003erealloc_ptr,\n+\t\t\t  (unsigned long)addr, \"realloc_after_flush\");\n+\t\tASSERT_EQ(*addr, skel-\u003erodata-\u003enew_marker, \"new_marker\");\n+\t}\n+out:\n+\tarena_race__destroy(skel);\n+}\n+\n+/*\n+ * Force a race between a faulting thread in userspace and a\n+ * free operation on the arena.\n+ */\n+static void test_fault_free_realloc(void)\n+{\n+\tstruct fault_thread_ctx fault = {};\n+\tstruct arena_race *skel;\n+\tpthread_t fault_thread;\n+\tbool fault_created = false;\n+\t__u64 *addr;\n+\t__u64 expected, value;\n+\tint err, i;\n+\n+\tskel = setup_arena(\u0026addr, false);\n+\tif (!skel)\n+\t\treturn;\n+\n+\tskel-\u003ebss-\u003erealloc_after_free = 1;\n+\n+\tfault.addr = addr;\n+\terr = pthread_create(\u0026fault_thread, NULL, fault_reader_thread, \u0026fault);\n+\tif (!ASSERT_OK(err, \"pthread_create_fault\"))\n+\t\tgoto out;\n+\tfault_created = true;\n+\n+\tfor (i = 0; i \u003c 1000; i++) {\n+\t\tLIBBPF_OPTS(bpf_test_run_opts, opts);\n+\n+\t\terr = bpf_prog_test_run_opts(bpf_program__fd(skel-\u003eprogs.free_page),\n+\t\t\t\t\t     \u0026opts);\n+\t\tif (err) {\n+\t\t\tASSERT_OK(err, \"free_realloc\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (opts.retval) {\n+\t\t\tASSERT_OK(opts.retval, \"free_realloc\");\n+\t\t\tbreak;\n+\t\t}\n+\t\tvalue = *addr;\n+\t\texpected = skel-\u003ebss-\u003ecurrent_marker;\n+\t\tif (value != expected) {\n+\t\t\tASSERT_EQ(value, expected, \"marker_after_realloc\");\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tWRITE_ONCE(fault.stop, 1);\n+\tif (fault_created) {\n+\t\tASSERT_OK(pthread_join(fault_thread, NULL), \"pthread_join_fault\");\n+\t\tfault_created = false;\n+\t}\n+out:\n+\tWRITE_ONCE(fault.stop, 1);\n+\tif (fault_created)\n+\t\tpthread_join(fault_thread, NULL);\n+\tarena_race__destroy(skel);\n+}\n+\n+void serial_test_arena_race(void)\n+{\n+\tcpu_set_t cpuset;\n+\tint err;\n+\n+\terr = sched_getaffinity(0, sizeof(cpuset), \u0026cpuset);\n+\tif (!ASSERT_OK(err, \"sched_getaffinity\"))\n+\t\treturn;\n+\tif (CPU_COUNT(\u0026cpuset) \u003c 2) {\n+\t\tprintf(\"%s:SKIP: at least two runnable CPUs are required\\n\", __func__);\n+\t\ttest__skip();\n+\t\treturn;\n+\t}\n+\n+\tif (test__start_subtest(\"free_before_flush\"))\n+\t\ttest_free_before_flush(false);\n+\tif (test__start_subtest(\"deferred_free_before_flush\"))\n+\t\ttest_free_before_flush(true);\n+\tif (test__start_subtest(\"fault_free_realloc\"))\n+\t\ttest_fault_free_realloc();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/arena_race.c b/tools/testing/selftests/bpf/progs/arena_race.c\nnew file mode 100644\nindex 0000000000000..56ebfd7273247\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/arena_race.c\n@@ -0,0 +1,159 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#define BPF_NO_KFUNC_PROTOTYPES\n+#include \u003cvmlinux.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_experimental.h\"\n+#include \u003cbpf_arena_common.h\u003e\n+\n+const volatile __u64 old_marker = 0x1111222233334444ULL;\n+const volatile __u64 new_marker = 0x5555666677778888ULL;\n+\n+struct {\n+\t__uint(type, BPF_MAP_TYPE_ARENA);\n+\t__uint(map_flags, BPF_F_MMAPABLE);\n+\t__uint(max_entries, 4);\n+#ifdef __TARGET_ARCH_arm64\n+\t__ulong(map_extra, 0x1ull \u003c\u003c 32);\n+#else\n+\t__ulong(map_extra, 0x1ull \u003c\u003c 44);\n+#endif\n+} arena SEC(\".maps\");\n+\n+bool skip;\n+\n+void __arena *ptr;\n+void __arena *realloc_ptr;\n+bool realloc_after_free;\n+__u64 current_marker;\n+__u64 marker_seq;\n+\n+int target_tid;\n+int pause_on_flush;\n+int flush_entered;\n+int release;\n+int timed_out;\n+\n+__u64 trigger_pid_tgid;\n+long trigger_syscall;\n+int deferred_free;\n+int worker_armed;\n+int worker_exited;\n+\n+static __always_inline void wait_for_release(void)\n+{\n+\twhile (!*(volatile int *)\u0026release \u0026\u0026 can_loop)\n+\t\t;\n+\tif (!*(volatile int *)\u0026release)\n+\t\ttimed_out = 1;\n+}\n+\n+SEC(\"syscall\")\n+int alloc_old(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)\n+\t__u64 __arena *p;\n+\tchar __arena *base = arena_base(\u0026arena);\n+\n+\trealloc_ptr = NULL;\n+\tptr = bpf_arena_alloc_pages(\u0026arena, base + __PAGE_SIZE, 1,\n+\t\t\t\t    NUMA_NO_NODE, 0);\n+\tif (!ptr)\n+\t\treturn 1;\n+\tp = (__u64 __arena *)ptr;\n+\t*p = old_marker;\n+\tcurrent_marker = old_marker;\n+#else\n+\tskip = true;\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+int free_page(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)\n+\t__u64 __arena *p;\n+\t__u64 marker;\n+\n+\tif (!ptr)\n+\t\treturn 1;\n+\tbpf_arena_free_pages(\u0026arena, ptr, 1);\n+\tif (!realloc_after_free)\n+\t\treturn 0;\n+\n+\tmarker = new_marker + ++marker_seq;\n+\trealloc_ptr = bpf_arena_alloc_pages(\u0026arena, ptr, 1, NUMA_NO_NODE, 0);\n+\tif (realloc_ptr)\n+\t\tptr = realloc_ptr;\n+\telse\n+\t\trealloc_ptr = ptr;\n+\tp = (__u64 __arena *)ptr;\n+\t*p = marker;\n+\tcurrent_marker = marker;\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"syscall\")\n+int try_realloc(void *ctx)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)\n+\t__u64 __arena *p;\n+\n+\trealloc_ptr = bpf_arena_alloc_pages(\u0026arena, ptr, 1, NUMA_NO_NODE, 0);\n+\tif (realloc_ptr) {\n+\t\tptr = realloc_ptr;\n+\t\tp = (__u64 __arena *)realloc_ptr;\n+\t\t*p = new_marker;\n+\t\tcurrent_marker = new_marker;\n+\t}\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"tp_btf/sys_enter\")\n+int BPF_PROG(deferred_free_prog, struct pt_regs *regs, long id)\n+{\n+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)\n+\tif (!deferred_free || bpf_get_current_pid_tgid() != trigger_pid_tgid ||\n+\t    id != trigger_syscall)\n+\t\treturn 0;\n+\n+\tdeferred_free = 0;\n+\t/* The worker can run on another CPU before the kfunc returns. */\n+\tworker_armed = 1;\n+\tbpf_arena_free_pages(\u0026arena, ptr, 1);\n+#endif\n+\treturn 0;\n+}\n+\n+SEC(\"fentry/arena_free_worker\")\n+int BPF_PROG(trace_free_worker, struct work_struct *work)\n+{\n+\tif (worker_armed \u0026\u0026 !target_tid)\n+\t\ttarget_tid = (__u32)bpf_get_current_pid_tgid();\n+\treturn 0;\n+}\n+\n+SEC(\"fexit/arena_free_worker\")\n+int BPF_PROG(trace_free_worker_ret, struct work_struct *work)\n+{\n+\tif ((__u32)bpf_get_current_pid_tgid() == target_tid)\n+\t\tworker_exited = 1;\n+\treturn 0;\n+}\n+\n+SEC(\"?fentry/flush_tlb_kernel_range\")\n+int BPF_PROG(trace_flush, unsigned long start, unsigned long end)\n+{\n+\tif (!pause_on_flush ||\n+\t    (__u32)bpf_get_current_pid_tgid() != target_tid)\n+\t\treturn 0;\n+\tflush_entered = 1;\n+\twait_for_release();\n+\treturn 0;\n+}\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n"
}

2/1 2026/09/25 11:38 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "arena_vm_fault",
    "arena_alloc_pages",
    "arena_free_pages",
    "arena_free_worker",
    "range_tree_set_avail",
    "range_tree_set_unavail",
    "range_tree_remove_unavail",
    "range_tree_make_avail",
    "range_tree_clear",
    "bpf_arena_alloc_pages",
    "bpf_arena_free_pages"
  ],
  "Reasoning": "The patch refactors BPF arena memory management and range tree handling to prevent races between concurrent arena page allocation/faulting and freeing/TLB flushing. It modifies arena page fault handling (arena_vm_fault), freeing workflows (arena_free_pages, arena_free_worker), and introduces range node availability tracking in the range tree, along with multiple new WARN_ON_ONCE assertions. This code is reachable from user space on amd64 via BPF system calls (creating BPF_MAP_TYPE_ARENA maps), mmap(), page faults, and arena kfuncs.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 9a0e7021e2025ed16d20a20f6bcbe33bb7e16186
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 25 11:38:45 2026 +0000

    syz-cluster: applied patch under review

diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..299525f93bbda 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -72,10 +72,18 @@ struct bpf_arena {
 static void arena_free_worker(struct work_struct *work);
 static void arena_free_irq(struct irq_work *iw);
 
+enum arena_free_span_state {
+	ARENA_FREE_SPAN_NOT_STARTED,	/* Freeing not started */
+	ARENA_FREE_SPAN_UNAVAIL,	/* Region cleared & unavailable */
+	ARENA_FREE_SPAN_ZAPPED,		/* Region zapped and cleared */
+	ARENA_FREE_SPAN_FAILED,		/* Freeing operation cannot continue */
+};
+
 struct arena_free_span {
 	struct llist_node node;
 	unsigned long uaddr;
 	u32 page_cnt;
+	enum arena_free_span_state state;
 };
 
 u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)
@@ -317,7 +325,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
 		goto err_free_arena;
 
 	range_tree_init(&arena->rt);
-	err = range_tree_set(&arena->rt, 0, attr->max_entries);
+	err = range_tree_set_avail(&arena->rt, 0, attr->max_entries);
 	if (err)
 		goto err_free_scratch;
 	mutex_init(&arena->lock);
@@ -481,21 +489,22 @@ 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 range_node *unavail_node;
 	struct page *page;
 	long kbase, kaddr;
 	unsigned long flags;
+	vm_fault_t ret_fault;
 	int ret;
 
 	kbase = bpf_arena_get_kern_vm_start(arena);
 	kaddr = kbase + (u32)(vmf->address);
 
-	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.
-		 */
+	ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags);
+	/* If we are deadlocking somehow, no way to ensure forward progress. */
+	if (ret == -EDEADLK)
 		return VM_FAULT_SIGBUS;
+	if (ret)
+		goto retry;
 
 	page = vmalloc_to_page((void *)kaddr);
 	if (page) {
@@ -513,6 +522,14 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
 		goto out_sigsegv_memcg;
 
 	ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
+	/* If a range is unavailable, try again. */
+	if (ret == -EAGAIN) {
+		raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+		bpf_map_memcg_exit(old_memcg, new_memcg);
+
+		goto retry;
+	}
+
 	if (ret)
 		goto out_sigsegv_memcg;
 
@@ -520,28 +537,61 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
 	/* 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);
+		range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
 		goto out_sigsegv_memcg;
 	}
 
 	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);
+		range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
 		free_pages_nolock(page, 0);
 		goto out_sigsegv_memcg;
 	}
 	flush_vmap_cache(kaddr, PAGE_SIZE);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 out:
-	page_ref_add(page, 1);
+	/* Reserve the page while installing its user PTE without the arena lock. */
+	bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
+	unavail_node = range_tree_set_unavail(&arena->rt, vmf->pgoff, 1);
+	bpf_map_memcg_exit(old_memcg, new_memcg);
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
-	vmf->page = page;
-	return 0;
+	if (IS_ERR(unavail_node)) {
+		ret = PTR_ERR(unavail_node);
+		if (ret == -EAGAIN)
+			goto retry;
+		return VM_FAULT_OOM;
+	}
+
+	ret_fault = vmf_insert_page(vmf->vma, vmf->address, page);
+
+	while ((ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags))) {
+		/* If we somehow deadlocked stop trying to take the lock. */
+		if (ret == -EDEADLK)
+			return VM_FAULT_SIGBUS;
+
+		cond_resched();
+	}
+
+	ret = range_tree_remove_unavail(&arena->rt, vmf->pgoff, 1);
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+	WARN_ON_ONCE(ret);
+	return ret_fault;
 out_sigsegv_memcg:
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 out_sigsegv:
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 	return VM_FAULT_SIGSEGV;
+
+retry:
+
+	/* Only for special cases (GUP/device drivers). */
+	if (!(vmf->flags & FAULT_FLAG_ALLOW_RETRY))
+		return VM_FAULT_SIGBUS;
+
+	if (!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
+		release_fault_lock(vmf);
+
+	return VM_FAULT_RETRY;
 }
 
 static const struct vm_operations_struct arena_vm_ops = {
@@ -621,7 +671,7 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
 	 * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from
 	 * being copied into the child process on fork.
 	 */
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);
+	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY | VM_MIXEDMAP);
 	vma->vm_ops = &arena_vm_ops;
 	return 0;
 }
@@ -766,7 +816,7 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 	return clear_lo32(arena->user_vm_start) + uaddr32;
 out:
-	range_tree_set(&arena->rt, pgoff + mapped, page_cnt - mapped);
+	range_tree_set_avail(&arena->rt, pgoff + mapped, page_cnt - mapped);
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 	if (mapped) {
 		flush_vmap_cache(kern_vm_start + uaddr32, mapped << PAGE_SHIFT);
@@ -847,6 +897,7 @@ static void zap_pages(struct bpf_arena *arena, long uaddr, long page_cnt)
 static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)
 {
 	struct mem_cgroup *new_memcg, *old_memcg;
+	struct range_node *unavail_node = NULL;
 	u64 full_uaddr, uaddr_end;
 	long kaddr, pgoff;
 	struct page *page;
@@ -855,6 +906,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 	struct arena_free_span *s;
 	struct clear_range_data cdata;
 	unsigned long flags;
+	bool release_only = false;
 	int ret = 0;
 
 	/* only aligned lower 32-bit are relevant */
@@ -881,7 +933,20 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 	if (ret)
 		goto defer;
 
-	range_tree_set(&arena->rt, pgoff, page_cnt);
+	unavail_node = range_tree_set_unavail(&arena->rt, pgoff, page_cnt);
+	if (IS_ERR(unavail_node)) {
+		ret = PTR_ERR(unavail_node);
+		raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+		/*
+		 * For -EAGAIN: An overlapping fault reserves
+		 * the range before installing its PTE.
+		 */
+		if (ret == -ENOMEM || ret == -EAGAIN)
+			goto defer;
+		WARN_ON_ONCE(ret);
+		bpf_map_memcg_exit(old_memcg, new_memcg);
+		return;
+	}
 
 	init_llist_head(&free_pages);
 	cdata.arena = arena;
@@ -911,6 +976,16 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 			zap_pages(arena, full_uaddr, 1);
 		__free_page(page);
 	}
+
+	ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags);
+	if (ret) {
+		release_only = true;
+		goto defer;
+	}
+
+	ret = range_tree_make_avail(&arena->rt, pgoff, page_cnt);
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+	WARN_ON_ONCE(ret);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 
 	return;
@@ -918,16 +993,33 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 defer:
 	s = kmalloc_nolock(sizeof(struct arena_free_span), __GFP_ACCOUNT, -1);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
-	if (!s)
+	if (!s) {
 		/*
-		 * If allocation fails in non-sleepable context, pages are intentionally left
-		 * inaccessible (leaked) until the arena is destroyed. Cleanup or retries are not
-		 * possible here, so we intentionally omit them for safety.
+		 * Directly mark the region available. Unavailable regions must
+		 * always be transient, and a permanent one breaks the assumptions
+		 * made in the allocation/faulting code. The operation is safe because
+		 * unavailable nodes are not modified by the range tree code without a reference
+		 * to the node. The modification from unavailable to available also does
+		 * not require touching anything in the tree apart from the node itself,
+		 * so it can be done locklessly. The downside is fragmentation in the tree,
+		 * since we cannot merge with neighbors, but this is a) safe and b) better
+		 * than leaking the range.
 		 */
+		if (release_only)
+			range_node_mark_available(unavail_node);
+
+		/*
+		 * If we haven't even zapped the pages, intentionally leave them
+		 * inaccessible (leaked) until the arena is destroyed. Cleanup or
+		 * retries are not possible here, so we intentionally omit them for safety.
+		 */
+
 		return;
+	}
 
 	s->page_cnt = page_cnt;
 	s->uaddr = uaddr;
+	s->state = release_only ? ARENA_FREE_SPAN_ZAPPED : ARENA_FREE_SPAN_NOT_STARTED;
 	llist_add(&s->node, &arena->free_spans);
 	irq_work_queue(&arena->free_irq);
 }
@@ -976,6 +1068,7 @@ static void arena_free_worker(struct work_struct *work)
 	struct mem_cgroup *new_memcg, *old_memcg;
 	struct llist_node *list, *pos, *t;
 	struct arena_free_span *s;
+	struct range_node *unavail_node;
 	u64 arena_vm_start, user_vm_start;
 	struct llist_head free_pages;
 	struct clear_range_data cdata;
@@ -983,6 +1076,8 @@ static void arena_free_worker(struct work_struct *work)
 	unsigned long full_uaddr;
 	long kaddr, page_cnt, pgoff;
 	unsigned long flags;
+	bool retry = false;
+	int ret;
 
 	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
 		schedule_work(work);
@@ -998,23 +1093,48 @@ static void arena_free_worker(struct work_struct *work)
 	user_vm_start = bpf_arena_get_user_vm_start(arena);
 
 	list = llist_del_all(&arena->free_spans);
-	llist_for_each(pos, list) {
+	llist_for_each_safe(pos, t, list) {
 		s = llist_entry(pos, struct arena_free_span, node);
+		if (s->state != ARENA_FREE_SPAN_NOT_STARTED)
+			continue;
+
 		page_cnt = s->page_cnt;
-		kaddr = arena_vm_start + s->uaddr;
 		pgoff = compute_pgoff(arena, s->uaddr);
+		kaddr = arena_vm_start + s->uaddr;
+
+		unavail_node = range_tree_set_unavail(&arena->rt, pgoff, page_cnt);
+		if (IS_ERR(unavail_node)) {
+			ret = PTR_ERR(unavail_node);
+			/* Kick off another attempt at the end of this call. */
+			if (ret == -EAGAIN)
+				continue;
+
+			/*
+			 * An -ENOMEM failure is the same failure mode as in
+			 * the defer: path of arena_free_pages(). Do not treat
+			 * the leak as a bug.
+			 */
+			if (ret != -ENOMEM)
+				WARN_ON_ONCE(ret);
+
+			s->state = ARENA_FREE_SPAN_FAILED;
+			continue;
+		}
+
+		s->state = ARENA_FREE_SPAN_UNAVAIL;
 
 		/* clear ptes and collect pages in free_pages llist */
 		apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
 					     apply_range_clear_cb, &cdata);
-
-		range_tree_set(&arena->rt, pgoff, page_cnt);
 	}
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 
-	/* Iterate the list again without holding spinlock to do the tlb flush and zap_pages */
+	/* Keep ranges unavailable until their stale translations are gone. */
 	llist_for_each_safe(pos, t, list) {
 		s = llist_entry(pos, struct arena_free_span, node);
+		if (s->state != ARENA_FREE_SPAN_UNAVAIL)
+			continue;
+
 		page_cnt = s->page_cnt;
 		full_uaddr = clear_lo32(user_vm_start) + s->uaddr;
 		kaddr = arena_vm_start + s->uaddr;
@@ -1025,7 +1145,11 @@ static void arena_free_worker(struct work_struct *work)
 		/* remove pages from user vmas */
 		zap_pages(arena, full_uaddr, page_cnt);
 
-		kfree_nolock(s);
+		/*
+		 * Used to avoid zapping twice if we fail the lock acquisition
+		 * below and rerun the span through this function.
+		 */
+		s->state = ARENA_FREE_SPAN_ZAPPED;
 	}
 
 	/* free all pages collected by apply_to_existing_page_range() in the first loop */
@@ -1034,7 +1158,56 @@ static void arena_free_worker(struct work_struct *work)
 		__free_page(page);
 	}
 
+	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
+		llist_for_each_safe(pos, t, list) {
+			s = llist_entry(pos, struct arena_free_span, node);
+
+			if (s->state == ARENA_FREE_SPAN_FAILED) {
+				kfree_nolock(s);
+				continue;
+			}
+
+			llist_add(pos, &arena->free_spans);
+			retry = true;
+		}
+		goto done;
+	}
+
+	llist_for_each_safe(pos, t, list) {
+		s = llist_entry(pos, struct arena_free_span, node);
+
+		/* Remove the spans of failed allocations. */
+		if (s->state == ARENA_FREE_SPAN_FAILED) {
+			kfree_nolock(s);
+			continue;
+		}
+
+		if (s->state == ARENA_FREE_SPAN_NOT_STARTED) {
+			llist_add(pos, &arena->free_spans);
+			retry = true;
+			continue;
+		}
+
+		page_cnt = s->page_cnt;
+		pgoff = compute_pgoff(arena, s->uaddr);
+		/*
+		 * This range tree operation does not allocate memory,
+		 * and so should never fail regardless of contention
+		 * or memory pressure. This is in contrast to regular
+		 * inserts that _can_ fail under memory pressure and
+		 * force us to defer the free.
+		 */
+		ret = range_tree_make_avail(&arena->rt, pgoff, page_cnt);
+		WARN_ON_ONCE(ret);
+		kfree_nolock(s);
+	}
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+
+done:
 	bpf_map_memcg_exit(old_memcg, new_memcg);
+
+	if (retry)
+		schedule_work(work);
 }
 
 static void arena_free_irq(struct irq_work *iw)
diff --git a/kernel/bpf/range_tree.c b/kernel/bpf/range_tree.c
index 2f28886f3ff75..62ebf4df51cb4 100644
--- a/kernel/bpf/range_tree.c
+++ b/kernel/bpf/range_tree.c
@@ -3,6 +3,7 @@
 #include <linux/interval_tree_generic.h>
 #include <linux/slab.h>
 #include <linux/bpf.h>
+#include <linux/err.h>
 #include "range_tree.h"
 
 /*
@@ -39,8 +40,29 @@ struct range_node {
 	u32 rn_start;
 	u32 rn_last; /* inclusive */
 	u32 __rn_subtree_last;
+	bool available; /* range is available for allocating. */
 };
 
+/* Is the range available for merging? */
+static inline bool range_available(struct range_node *rn)
+{
+	/* Pairs with smp_store_release() in range_node_mark_available(). */
+	return rn && smp_load_acquire(&rn->available);
+}
+
+/*
+ * Mark a node as available. Designed to be used locklessly
+ * as a last-ditch effort to avoid leaking unavailable range
+ * tree nodes when all attempts to directly or indirectly
+ * free an arena region has failed. See arena_free_pages()
+ * for more info.
+ */
+void range_node_mark_available(struct range_node *rn)
+{
+	/* Pairs with smp_load_acquire() in range_available(). */
+	smp_store_release(&rn->available, true);
+}
+
 static struct range_node *rb_to_range_node(struct rb_node *rb)
 {
 	return rb_entry(rb, struct range_node, rb_range_size);
@@ -55,20 +77,30 @@ static u32 rn_size(struct range_node *rn)
 static inline struct range_node *__find_range(struct range_tree *rt, u32 len)
 {
 	struct rb_node *rb = rt->range_size_root.rb_root.rb_node;
-	struct range_node *best = NULL;
+	struct rb_node *best = NULL;
+	struct range_node *rn;
 
 	while (rb) {
-		struct range_node *rn = rb_to_range_node(rb);
+		rn = rb_to_range_node(rb);
 
 		if (len <= rn_size(rn)) {
-			best = rn;
+			best = rb;
 			rb = rb->rb_right;
 		} else {
 			rb = rb->rb_left;
 		}
 	}
 
-	return best;
+	/* Filter unavailable ranges. */
+	while (best) {
+		rn = rb_to_range_node(best);
+		if (range_available(rn))
+			return rn;
+
+		best = rb_prev(best);
+	}
+
+	return NULL;
 }
 
 s64 range_tree_find(struct range_tree *rt, u32 len)
@@ -135,10 +167,19 @@ range_it_iter_first(struct range_tree *rt, u32 start, u32 last)
 /* Clear the range in this range tree */
 int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 {
+	u32 first = start;
 	u32 last = start + len - 1;
 	struct range_node *new_rn;
 	struct range_node *rn;
 
+	/* Scan for unavailable ranges and try again if so. */
+	while ((rn = range_it_iter_first(rt, first, last))) {
+		if (!range_available(rn))
+			return -EAGAIN;
+
+		first = rn->rn_last + 1;
+	}
+
 	while ((rn = range_it_iter_first(rt, start, last))) {
 		if (rn->rn_start < start && rn->rn_last > last) {
 			u32 old_last = rn->rn_last;
@@ -153,6 +194,7 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 						NUMA_NO_NODE);
 			if (!new_rn)
 				return -ENOMEM;
+			new_rn->available = rn->available;
 			new_rn->rn_start = last + 1;
 			new_rn->rn_last = old_last;
 			range_it_insert(new_rn, rt);
@@ -180,32 +222,31 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)
 {
 	u32 last = start + len - 1;
-	struct range_node *left;
+	struct range_node *rn;
 
-	/* Is this whole range set ? */
-	left = range_it_iter_first(rt, start, last);
-	if (left && left->rn_start <= start && left->rn_last >= last)
-		return 0;
+	for ((rn = range_it_iter_first(rt, start, last)); rn != NULL;
+		rn = __range_it_iter_next(rn, start, last)) {
+		/* Make sure the range covers the start */
+		if (rn->rn_start > start)
+			return -ESRCH;
+
+		/* If it covers the entire range we're done. */
+		if (rn->rn_last >= last)
+			return 0;
+
+		start = rn->rn_last + 1;
+	}
+
+	/* No range to cover [start, last] */
 	return -ESRCH;
 }
 
-/* Set the range in this range tree */
-int range_tree_set(struct range_tree *rt, u32 start, u32 len)
+/* Do we have adjacent ranges (and do not overlap with them)? */
+static int range_get_adjacent(struct range_tree *rt, u32 start, u32 last,
+		struct range_node **leftp, struct range_node **rightp)
 {
-	u32 last = start + len - 1;
 	struct range_node *right;
 	struct range_node *left;
-	int err;
-
-	/* Is this whole range already set ? */
-	left = range_it_iter_first(rt, start, last);
-	if (left && left->rn_start <= start && left->rn_last >= last)
-		return 0;
-
-	/* Clear out everything in the range we want to set. */
-	err = range_tree_clear(rt, start, len);
-	if (err)
-		return err;
 
 	/* Do we have a left-adjacent range ? */
 	left = range_it_iter_first(rt, start - 1, start - 1);
@@ -217,31 +258,163 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)
 	if (right && right->rn_start != last + 1)
 		return -EFAULT;
 
-	if (left && right) {
+	*leftp = left;
+	*rightp = right;
+
+	return 0;
+}
+
+/*
+ * Merge with adjacent available ranges if possible. The new [start, last]
+ * has already been confirmed to be adjacent with left/right by the caller.
+ */
+static int range_tree_merge(struct range_tree *rt, u32 start, u32 last,
+		struct range_node *left, struct range_node *right)
+{
+	if (range_available(left) && range_available(right)) {
 		/* Combine left and right adjacent ranges */
 		range_it_remove(left, rt);
 		range_it_remove(right, rt);
 		left->rn_last = right->rn_last;
 		range_it_insert(left, rt);
 		kfree_nolock(right);
-	} else if (left) {
+	} else if (range_available(left)) {
 		/* Combine with the left range */
 		range_it_remove(left, rt);
 		left->rn_last = last;
 		range_it_insert(left, rt);
-	} else if (right) {
+	} else if (range_available(right)) {
 		/* Combine with the right range */
 		range_it_remove(right, rt);
 		right->rn_start = start;
 		range_it_insert(right, rt);
 	} else {
-		left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
-		if (!left)
-			return -ENOMEM;
-		left->rn_start = start;
-		left->rn_last = last;
-		range_it_insert(left, rt);
+		/* No merge available. */
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/* Make a range available, possibly merging. */
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len)
+{
+	u32 last = start + len - 1;
+	struct range_node *rn;
+	struct range_node *right;
+	struct range_node *left;
+	int err;
+
+	/*
+	 * Confirm the range exists is unavailable,
+	 * and fits the requested range exactly.
+	 */
+	rn = range_it_iter_first(rt, start, last);
+	if (!rn || rn->available)
+		return -EINVAL;
+
+	if (rn->rn_start != start || rn->rn_last != last)
+		return -EINVAL;
+
+	err = range_get_adjacent(rt, start, last, &left, &right);
+	if (err)
+		return err;
+
+	/* If no merging required, just make available. */
+	if (!range_available(left) && !range_available(right)) {
+		rn->available = true;
+		return 0;
 	}
+
+	/* Can merge, remove the range already. */
+	start = rn->rn_start;
+	last = rn->rn_last;
+	range_it_remove(rn, rt);
+	kfree_nolock(rn);
+
+	return range_tree_merge(rt, start, last, left, right);
+}
+
+/* Set the range in this range tree */
+static int range_tree_set(struct range_tree *rt, u32 start, u32 len, bool available,
+			  struct range_node **new_rn)
+{
+	u32 last = start + len - 1;
+	struct range_node *right;
+	struct range_node *left;
+	int err;
+
+	/* Is this whole range already set ? */
+	left = range_it_iter_first(rt, start, last);
+	if (left && left->rn_start <= start && left->rn_last >= last &&
+	    range_available(left) && available)
+		return 0;
+
+	/* Clear out everything in the range we want to set. */
+	err = range_tree_clear(rt, start, len);
+	if (err)
+		return err;
+
+	/* Get adjacent ranges and check for overlaps. */
+	err = range_get_adjacent(rt, start, last, &left, &right);
+	if (err)
+		return err;
+
+	/*
+	 * If the range is not available for allocation, don't merge.
+	 * Unavailable ranges are in the process of being freed and should
+	 * be imminently marked available, so merging them with other
+	 * unavailable ranges will just lead to splitting the range back
+	 * almost immediately.
+	 */
+	if (available) {
+		err = range_tree_merge(rt, start, last, left, right);
+		if (!err)
+			return 0;
+	}
+
+	left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
+	if (!left)
+		return -ENOMEM;
+	left->available = available;
+	left->rn_start = start;
+	left->rn_last = last;
+	range_it_insert(left, rt);
+	if (new_rn)
+		*new_rn = left;
+
+	return 0;
+}
+
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len)
+{
+	return range_tree_set(rt, start, len, true, NULL);
+}
+
+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len)
+{
+	struct range_node *rn = NULL;
+	int err;
+
+	err = range_tree_set(rt, start, len, false, &rn);
+	if (err)
+		return ERR_PTR(err);
+	if (WARN_ON_ONCE(!rn))
+		return ERR_PTR(-EINVAL);
+	return rn;
+}
+
+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len)
+{
+	u32 last = start + len - 1;
+	struct range_node *rn;
+
+	rn = range_it_iter_first(rt, start, last);
+	if (!rn || rn->available || rn->rn_start != start || rn->rn_last != last)
+		return -EINVAL;
+
+	range_it_remove(rn, rt);
+	kfree_nolock(rn);
 	return 0;
 }
 
diff --git a/kernel/bpf/range_tree.h b/kernel/bpf/range_tree.h
index ff0b9110eb715..79295abe36845 100644
--- a/kernel/bpf/range_tree.h
+++ b/kernel/bpf/range_tree.h
@@ -10,11 +10,17 @@ struct range_tree {
 	struct rb_root_cached range_size_root;
 };
 
+struct range_node;
+
 void range_tree_init(struct range_tree *rt);
 void range_tree_destroy(struct range_tree *rt);
 
 int range_tree_clear(struct range_tree *rt, u32 start, u32 len);
-int range_tree_set(struct range_tree *rt, u32 start, u32 len);
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len);
+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len);
+void range_node_mark_available(struct range_node *rn);
+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len);
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len);
 int is_range_tree_set(struct range_tree *rt, u32 start, u32 len);
 s64 range_tree_find(struct range_tree *rt, u32 len);
 
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_race.c b/tools/testing/selftests/bpf/prog_tests/arena_race.c
new file mode 100644
index 0000000000000..c2da611421f3a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_race.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/syscall.h>
+#include <test_progs.h>
+
+#include "arena_race.skel.h"
+
+struct free_thread_ctx {
+	struct arena_race *skel;
+	int err;
+	__u32 retval;
+};
+
+struct fault_thread_ctx {
+	__u64 *addr;
+	int stop;
+};
+
+static int run_prog(struct bpf_program *prog, const char *name)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	int err;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+	return ASSERT_OK(err, name) && ASSERT_OK(opts.retval, name) ? 0 : -1;
+}
+
+/* Trigger the sleepable free page path. */
+static void *run_free_thread(void *arg)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct free_thread_ctx *ctx = arg;
+
+	ctx->skel->bss->target_tid = sys_gettid();
+	ctx->err = bpf_prog_test_run_opts(
+		bpf_program__fd(ctx->skel->progs.free_page), &opts);
+	ctx->retval = opts.retval;
+	return NULL;
+}
+
+/* Continuously fault in the address. */
+static void *fault_reader_thread(void *arg)
+{
+	struct fault_thread_ctx *ctx = arg;
+
+	while (!READ_ONCE(ctx->stop))
+		(void)READ_ONCE(*ctx->addr);
+	return NULL;
+}
+
+static int wait_for(int *p)
+{
+	__u64 deadline = get_time_ns() + 5ULL * 1000 * 1000 * 1000;
+
+	while (!READ_ONCE(*p)) {
+		if (get_time_ns() > deadline)
+			return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+static struct arena_race *setup_arena(__u64 **addr, bool trace_flush)
+{
+	struct arena_race *skel;
+	size_t arena_sz;
+	char *base;
+	int err;
+
+	skel = arena_race__open();
+	if (!ASSERT_OK_PTR(skel, "open"))
+		return NULL;
+	err = bpf_program__set_autoload(skel->progs.trace_flush, trace_flush);
+	if (!ASSERT_OK(err, "trace_flush_autoload"))
+		goto err_out;
+
+	err = arena_race__load(skel);
+	if (!ASSERT_OK(err, "load"))
+		goto err_out;
+	err = arena_race__attach(skel);
+	if (!ASSERT_OK(err, "attach"))
+		goto err_out;
+
+	if (run_prog(skel->progs.alloc_old, "alloc_old"))
+		goto err_out;
+	if (skel->bss->skip) {
+		test__skip();
+		goto err_out;
+	}
+
+	base = bpf_map__initial_value(skel->maps.arena, &arena_sz);
+	if (!ASSERT_OK_PTR(base, "arena_base"))
+		goto err_out;
+	*addr = (__u64 *)(base + getpagesize());
+	if (!ASSERT_EQ((unsigned long)skel->bss->ptr, (unsigned long)*addr,
+		       "arena_ptr"))
+		goto err_out;
+	return skel;
+
+err_out:
+	arena_race__destroy(skel);
+	return NULL;
+}
+
+static void test_free_before_flush(bool deferred)
+{
+	struct free_thread_ctx ctx = {};
+	struct arena_race *skel;
+	pthread_t thread;
+	__u64 *addr;
+	bool thread_created = false, flush_seen = false, completed = false;
+	int err;
+
+	if (libbpf_find_vmlinux_btf_id("flush_tlb_kernel_range",
+					BPF_TRACE_FENTRY) <= 0) {
+		printf("%s:SKIP: flush_tlb_kernel_range is not an fentry target\n",
+		       __func__);
+		test__skip();
+		return;
+	}
+
+	skel = setup_arena(&addr, true);
+	if (!skel)
+		return;
+
+	/* Pause during a TLB flush to widen the race window. */
+	skel->bss->pause_on_flush = 1;
+
+	if (deferred) {
+		/*
+		 * Test the nonsleepable free path that gets
+		 * deferred to a worker in the kernel. We do
+		 * so by triggering the arena operation from
+		 * a nonsleepable tracepoint context.
+		 */
+		skel->bss->trigger_pid_tgid =
+			((__u64)getpid() << 32) | (__u32)sys_gettid();
+		skel->bss->trigger_syscall = SYS_getpgid;
+		skel->bss->deferred_free = 1;
+		if (!ASSERT_GE(syscall(SYS_getpgid, 0), 0, "deferred_free"))
+			goto release;
+	} else {
+		/*
+		 * Test the sleepable arena free path through a
+		 * syscall test prog.
+		 */
+		ctx.skel = skel;
+		err = pthread_create(&thread, NULL, run_free_thread, &ctx);
+		if (!ASSERT_OK(err, "pthread_create")) {
+			skel->bss->release = 1;
+			goto out;
+		}
+		thread_created = true;
+	}
+
+	/* Wait until the worker thread triggers a flush. */
+	err = wait_for(&skel->bss->flush_entered);
+	if (!ASSERT_OK(err, "flush_entered"))
+		goto release;
+
+	flush_seen = true;
+
+	/* Force a reallocation during the flush. */
+	run_prog(skel->progs.try_realloc, "realloc_before_flush");
+	ASSERT_NULL(skel->bss->realloc_ptr, "realloc_before_flush");
+
+release:
+	skel->bss->release = 1;
+	if (thread_created) {
+		ASSERT_OK(pthread_join(thread, NULL), "pthread_join");
+		thread_created = false;
+		completed = ASSERT_OK(ctx.err, "free_run") &&
+			    ASSERT_OK(ctx.retval, "free_retval");
+	} else if (skel->bss->target_tid) {
+		err = wait_for(&skel->bss->worker_exited);
+		completed = ASSERT_OK(err, "worker_exited");
+	}
+	ASSERT_FALSE(skel->bss->timed_out, "flush_timed_out");
+
+	if (flush_seen && completed &&
+	    !run_prog(skel->progs.try_realloc, "realloc_after_flush")) {
+		ASSERT_EQ((unsigned long)skel->bss->realloc_ptr,
+			  (unsigned long)addr, "realloc_after_flush");
+		ASSERT_EQ(*addr, skel->rodata->new_marker, "new_marker");
+	}
+out:
+	arena_race__destroy(skel);
+}
+
+/*
+ * Force a race between a faulting thread in userspace and a
+ * free operation on the arena.
+ */
+static void test_fault_free_realloc(void)
+{
+	struct fault_thread_ctx fault = {};
+	struct arena_race *skel;
+	pthread_t fault_thread;
+	bool fault_created = false;
+	__u64 *addr;
+	__u64 expected, value;
+	int err, i;
+
+	skel = setup_arena(&addr, false);
+	if (!skel)
+		return;
+
+	skel->bss->realloc_after_free = 1;
+
+	fault.addr = addr;
+	err = pthread_create(&fault_thread, NULL, fault_reader_thread, &fault);
+	if (!ASSERT_OK(err, "pthread_create_fault"))
+		goto out;
+	fault_created = true;
+
+	for (i = 0; i < 1000; i++) {
+		LIBBPF_OPTS(bpf_test_run_opts, opts);
+
+		err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.free_page),
+					     &opts);
+		if (err) {
+			ASSERT_OK(err, "free_realloc");
+			break;
+		}
+		if (opts.retval) {
+			ASSERT_OK(opts.retval, "free_realloc");
+			break;
+		}
+		value = *addr;
+		expected = skel->bss->current_marker;
+		if (value != expected) {
+			ASSERT_EQ(value, expected, "marker_after_realloc");
+			break;
+		}
+	}
+
+	WRITE_ONCE(fault.stop, 1);
+	if (fault_created) {
+		ASSERT_OK(pthread_join(fault_thread, NULL), "pthread_join_fault");
+		fault_created = false;
+	}
+out:
+	WRITE_ONCE(fault.stop, 1);
+	if (fault_created)
+		pthread_join(fault_thread, NULL);
+	arena_race__destroy(skel);
+}
+
+void serial_test_arena_race(void)
+{
+	cpu_set_t cpuset;
+	int err;
+
+	err = sched_getaffinity(0, sizeof(cpuset), &cpuset);
+	if (!ASSERT_OK(err, "sched_getaffinity"))
+		return;
+	if (CPU_COUNT(&cpuset) < 2) {
+		printf("%s:SKIP: at least two runnable CPUs are required\n", __func__);
+		test__skip();
+		return;
+	}
+
+	if (test__start_subtest("free_before_flush"))
+		test_free_before_flush(false);
+	if (test__start_subtest("deferred_free_before_flush"))
+		test_free_before_flush(true);
+	if (test__start_subtest("fault_free_realloc"))
+		test_fault_free_realloc();
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_race.c b/tools/testing/selftests/bpf/progs/arena_race.c
new file mode 100644
index 0000000000000..56ebfd7273247
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_race.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+
+const volatile __u64 old_marker = 0x1111222233334444ULL;
+const volatile __u64 new_marker = 0x5555666677778888ULL;
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 4);
+#ifdef __TARGET_ARCH_arm64
+	__ulong(map_extra, 0x1ull << 32);
+#else
+	__ulong(map_extra, 0x1ull << 44);
+#endif
+} arena SEC(".maps");
+
+bool skip;
+
+void __arena *ptr;
+void __arena *realloc_ptr;
+bool realloc_after_free;
+__u64 current_marker;
+__u64 marker_seq;
+
+int target_tid;
+int pause_on_flush;
+int flush_entered;
+int release;
+int timed_out;
+
+__u64 trigger_pid_tgid;
+long trigger_syscall;
+int deferred_free;
+int worker_armed;
+int worker_exited;
+
+static __always_inline void wait_for_release(void)
+{
+	while (!*(volatile int *)&release && can_loop)
+		;
+	if (!*(volatile int *)&release)
+		timed_out = 1;
+}
+
+SEC("syscall")
+int alloc_old(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+	char __arena *base = arena_base(&arena);
+
+	realloc_ptr = NULL;
+	ptr = bpf_arena_alloc_pages(&arena, base + __PAGE_SIZE, 1,
+				    NUMA_NO_NODE, 0);
+	if (!ptr)
+		return 1;
+	p = (__u64 __arena *)ptr;
+	*p = old_marker;
+	current_marker = old_marker;
+#else
+	skip = true;
+#endif
+	return 0;
+}
+
+SEC("syscall")
+int free_page(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+	__u64 marker;
+
+	if (!ptr)
+		return 1;
+	bpf_arena_free_pages(&arena, ptr, 1);
+	if (!realloc_after_free)
+		return 0;
+
+	marker = new_marker + ++marker_seq;
+	realloc_ptr = bpf_arena_alloc_pages(&arena, ptr, 1, NUMA_NO_NODE, 0);
+	if (realloc_ptr)
+		ptr = realloc_ptr;
+	else
+		realloc_ptr = ptr;
+	p = (__u64 __arena *)ptr;
+	*p = marker;
+	current_marker = marker;
+#endif
+	return 0;
+}
+
+SEC("syscall")
+int try_realloc(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+
+	realloc_ptr = bpf_arena_alloc_pages(&arena, ptr, 1, NUMA_NO_NODE, 0);
+	if (realloc_ptr) {
+		ptr = realloc_ptr;
+		p = (__u64 __arena *)realloc_ptr;
+		*p = new_marker;
+		current_marker = new_marker;
+	}
+#endif
+	return 0;
+}
+
+SEC("tp_btf/sys_enter")
+int BPF_PROG(deferred_free_prog, struct pt_regs *regs, long id)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	if (!deferred_free || bpf_get_current_pid_tgid() != trigger_pid_tgid ||
+	    id != trigger_syscall)
+		return 0;
+
+	deferred_free = 0;
+	/* The worker can run on another CPU before the kfunc returns. */
+	worker_armed = 1;
+	bpf_arena_free_pages(&arena, ptr, 1);
+#endif
+	return 0;
+}
+
+SEC("fentry/arena_free_worker")
+int BPF_PROG(trace_free_worker, struct work_struct *work)
+{
+	if (worker_armed && !target_tid)
+		target_tid = (__u32)bpf_get_current_pid_tgid();
+	return 0;
+}
+
+SEC("fexit/arena_free_worker")
+int BPF_PROG(trace_free_worker_ret, struct work_struct *work)
+{
+	if ((__u32)bpf_get_current_pid_tgid() == target_tid)
+		worker_exited = 1;
+	return 0;
+}
+
+SEC("?fentry/flush_tlb_kernel_range")
+int BPF_PROG(trace_flush, unsigned long start, unsigned long end)
+{
+	if (!pause_on_flush ||
+	    (__u32)bpf_get_current_pid_tgid() != target_tid)
+		return 0;
+	flush_entered = 1;
+	wait_for_release();
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";

3/2 2026/09/25 11:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 16665
							output: 29
							thoughts: 793
						

Thoughts:
**Analyzing the BPF Arena Patch: A Fuzzing Target**

Alright, let's dive into this patch. My initial assessment is that it's worth a very close look, and definitely a candidate for fuzzing. The commit touches several key files: `kernel/bpf/arena.c`, `kernel/bpf/range_tree.c`, `kernel/bpf/range_tree.h`, and related selftests. This immediately raises my attention.

My thinking is, first, let's get a handle on the changes themselves. It appears this patch is focused on the BPF arena, which is a relatively new BPF map type introduced in Linux 6.9 and later. The BPF arena allows userspace and BPF programs to share memory, making it a critical piece for inter-process communication. The patch is touching core functions like `arena_vm_fault`, `arena_map_alloc`, `arena_map_mmap`, `arena_alloc_pages`, `arena_free_pages`, and `arena_free_worker`. I'm seeing changes to `range_tree` functions, including adding new states to `range_tree`.

Looking deeper, the patch alters how `arena_vm_fault` handles retries, especially the handling of the `-EAGAIN` error. I'm noting the reacquisition of the spinlock with `cond_resched()`, potential races, and the introduction of new `WARN_ON_ONCE` checks, particularly in `arena_vm_fault`, `arena_free_pages`, `range_tree_set_unavail`, and `arena_free_worker`. The change to `arena_map_mmap` with `VM_MIXEDMAP` is also noteworthy.

The concurrency handling is changing, locking is being tweaked, page fault handling is modified, and retry loops are being added or modified. The fact that the arena uses a range tree for memory management is important. I'm also seeing state machine logic related to freeing memory, especially in `arena_free_worker`.

My immediate impression is that this is high-value target. This affects core kernel subsystems: BPF and memory management. We're dealing with reachable executable code through the standard `bpf()` syscall and `mmap()` on the arena map file descriptor. The fact that new `WARN_ON_ONCE` checks have been added is also a big red flag - that's a clear indication that the developers are concerned about potential issues.

Finally, let me double-check the Kconfig options. My next step will be to confirm that BPF arena is enabled and accessible in a standard QEMU environment on amd64. I'll use grepper or examine the Kconfig to verify this, but my gut tells me it is accessible and that's an opening.



4/2 2026/09/25 11:38 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "BPF_ARENA",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "error": "no matches"
}

5/2 2026/09/25 11:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18101
							output: 27
							thoughts: 32
						

Thoughts:
Here's my take on the thought process:

**Diving into the BPF Build Process**

Okay, so I need to understand how the `arena.o` and `range_tree.o` objects within the kernel's BPF (Berkeley Packet Filter) subsystem are built. My first instinct, as always, is to go straight to the source. Specifically, I should be looking at the `Makefile` located in the `kernel/bpf/` directory. That's the definitive guide, right? It'll show me the build rules, dependencies, and any special flags or configurations that are being applied to create those object files. Knowing how they're built will give me the context I need to understand their function and integration within the BPF framework. Let's see what's in there.



6/2 2026/09/25 11:38 tool grepper
0m Args:
{
  "Expression": "arena",
  "PathPrefix": "kernel/bpf/Makefile"
}

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"
}

7/2 2026/09/25 11:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18415
							output: 33
							thoughts: 21
						

Thoughts:
Here's my take on those thoughts, tailored for an expert audience:

**Examining the BPF Makefile**

Okay, so I need to dive into the build process for eBPF code. My immediate thought is to zero in on `kernel/bpf/Makefile`, specifically around lines 19-21. That's a common area for defining crucial build variables, object file inclusion rules, or even linking dependencies related to the BPF subsystem. I'm expecting to find either the core BPF object compilation directives, perhaps the inclusion of the common BPF headers, or potentially references to other relevant Makefiles or configuration options that are conditional based on the kernel version or feature flags. I anticipate that this section will provide key information to understanding the specifics of eBPF compilation within the kernel's build system. Let's see what's there... I want to verify how the BPF code is being compiled and linked.



8/2 2026/09/25 11:38 tool read-file
0m Args:
{
  "File": "kernel/bpf/Makefile",
  "FirstLine": 15,
  "LineCount": 15
}

Results:
{
  "Contents": "  15:\tobj-${CONFIG_BPF_LSM}\t  += bpf_inode_storage.o\n  16:\tobj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o\n  17:\tobj-$(CONFIG_BPF_JIT) += trampoline.o\n  18:\tobj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o stream.o\n  19:\tifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)\n  20:\tobj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o\n  21:\tendif\n  22:\tobj-$(CONFIG_BPF_JIT) += dispatcher.o\n  23:\tifeq ($(CONFIG_NET),y)\n  24:\tobj-$(CONFIG_BPF_SYSCALL) += devmap.o\n  25:\tobj-$(CONFIG_BPF_SYSCALL) += cpumap.o\n  26:\tobj-$(CONFIG_BPF_SYSCALL) += offload.o\n  27:\tobj-$(CONFIG_BPF_SYSCALL) += net_namespace.o\n  28:\tobj-$(CONFIG_BPF_SYSCALL) += tcx.o\n  29:\tendif\n"
}

9/2 2026/09/25 11:38 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19002
							output: 31
							thoughts: 67
						

Thoughts:
Here's my summary, as though I'm thinking through this myself:

**Investigating BPF Syscall Object File Inclusion**

Okay, so I see this line: `obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o` – that's the interesting bit.  Specifically, it's conditionally including `arena.o` and `range_tree.o` in the build based on the value of `CONFIG_BPF_SYSCALL`, and only when `CONFIG_MMU` and `CONFIG_64BIT` are both enabled ("y"). I need to understand *when* those object files are actually going to be built.  My next step has to be to drill down and check the Kconfig definition for `CONFIG_BPF_SYSCALL`.  I need to understand its dependencies, to figure out what's *really* controlling whether `arena.o` and `range_tree.o` get linked.  There's got to be some deeper, higher-level logic tying this all together. Let's see what Kconfig reveals.



10/2 2026/09/25 11:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "config BPF_SYSCALL",
  "PathPrefix": "init/Kconfig"
}

Results:
{
  "error": "no matches"
}

11/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19352
							output: 21
							thoughts: 17
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Investigating the Configuration for BPF_SYSCALL**

Alright, let's get down to brass tacks. I need to pinpoint the definition of `config BPF_SYSCALL` in the kernel configuration. I'm thinking I'll need to trace its origins, understand its dependencies, and see exactly *where* this crucial setting is being managed. Given my experience, I'm expecting this will involve spelunking through the Kconfig files, likely diving through the various architecture-specific or feature-specific directories. I’ll be looking for cross-references, conditional statements, and any hints about what other options control its visibility or state. Finding the definitive source of truth is key to understanding its implications within the BPF ecosystem and the kernel as a whole. Time to get digging.



12/2 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "config BPF_SYSCALL"
}

Results:
{
  "Output": "kernel/bpf/Kconfig=25=menu \"BPF subsystem\"\nkernel/bpf/Kconfig-26-\nkernel/bpf/Kconfig:27:config BPF_SYSCALL\nkernel/bpf/Kconfig-28-\tbool \"Enable bpf() system call\"\n"
}

13/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19658
							output: 34
							thoughts: 19
						

Thoughts:
Here's a summary of my thought process, keeping in mind my expert understanding:

**Navigating the BPF Kernel Configuration**

Okay, I need to understand something about the BPF configuration in the kernel. My initial thought is to zero in on `kernel/bpf/Kconfig`, specifically around line 27. I'm assuming that this area contains the crucial settings or options that control how BPF is integrated and enabled in this particular kernel version. I'll need to rapidly interpret the context provided by that section of the Kconfig file. I'll be looking for relevant `bool`, `tristate`, or `depends on` statements to get a handle on what features are available and what dependencies they have. I’m thinking there will be options to enable or disable different BPF features, maybe related to eBPF or specific BPF helpers. I’m also mentally preparing to evaluate the default settings and see if they align with my current needs or debugging tasks. Let's dig in and quickly assess the relevant configurations.



14/2 2026/09/25 11:39 tool read-file
0m Args:
{
  "File": "kernel/bpf/Kconfig",
  "FirstLine": 25,
  "LineCount": 25
}

Results:
{
  "Contents": "  25:\tmenu \"BPF subsystem\"\n  26:\t\n  27:\tconfig BPF_SYSCALL\n  28:\t\tbool \"Enable bpf() system call\"\n  29:\t\tselect BPF\n  30:\t\tselect IRQ_WORK\n  31:\t\tselect NEED_TASKS_RCU\n  32:\t\tselect TASKS_TRACE_RCU\n  33:\t\tselect BINARY_PRINTF\n  34:\t\tselect NET_SOCK_MSG if NET\n  35:\t\tselect NET_XGRESS if NET\n  36:\t\tselect PAGE_POOL if NET\n  37:\t\tdefault n\n  38:\t\thelp\n  39:\t\t  Enable the bpf() system call that allows to manipulate BPF programs\n  40:\t\t  and maps via file descriptors.\n  41:\t\n  42:\tconfig BPF_JIT\n  43:\t\tbool \"Enable BPF Just In Time compiler\"\n  44:\t\tdepends on BPF\n  45:\t\tdepends on HAVE_CBPF_JIT || HAVE_EBPF_JIT\n  46:\t\tselect EXECMEM\n  47:\t\thelp\n  48:\t\t  BPF programs are normally handled by a BPF interpreter. This option\n  49:\t\t  allows the kernel to generate native code when a program is loaded\n"
}

15/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20260
							output: 32
							thoughts: 86
						

Thoughts:
Here's my summary, as I delve into this BPF investigation:

**Investigating the BPF_SYSCALL Dependency Landscape**

Okay, so `BPF_SYSCALL` seems to be pretty independent from a configuration perspective. It's essentially free from any direct "depends on" beyond the usual suspects of architecture support and the presence of an MMU, which, of course, amd64 readily provides. Now, that's not the whole story. I need to make sure I haven't missed something lurking in the shadows, so my next step is to examine the `arena` and `bpf_arena` subsystems. Perhaps there are kfuncs, indirect dependencies, or other configuration options that tie into `BPF_SYSCALL` in a non-obvious way. I will start by reviewing `kernel/bpf/arena.c` and focus on areas around `bpf_arena_alloc_pages` or the arena registration process. That should give me a good picture of any potential relationships.



16/2 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "bpf_arena_alloc_pages",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/arena.c-39- * into kernel and user vma.\nkernel/bpf/arena.c:40: * bpf program can allocate a page via bpf_arena_alloc_pages() kfunc\nkernel/bpf/arena.c-41- * which will insert it into kernel vm_area.\n--\nkernel/bpf/arena.c=897=static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)\n--\nkernel/bpf/arena.c-1028- * Reserve an arena virtual address range without populating it. This call stops\nkernel/bpf/arena.c:1029: * bpf_arena_alloc_pages from adding pages to this range.\nkernel/bpf/arena.c-1030- */\n--\nkernel/bpf/arena.c=1220=__bpf_kfunc_start_defs();\nkernel/bpf/arena.c-1221-\nkernel/bpf/arena.c:1222:__bpf_kfunc void *bpf_arena_alloc_pages(void *p__map, void *addr__ign, u32 page_cnt,\nkernel/bpf/arena.c-1223-\t\t\t\t\tint node_id, u64 flags)\n--\nkernel/bpf/arena.c-1233-\nkernel/bpf/arena.c:1234:void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt,\nkernel/bpf/arena.c-1235-\t\t\t\t\t  int node_id, u64 flags)\n--\nkernel/bpf/arena.c-1245-\nkernel/bpf/arena.c:1246:void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,\nkernel/bpf/arena.c-1247-\t\t\t\t      int node_id, u64 flags)\n--\nkernel/bpf/arena.c=1293=BTF_KFUNCS_START(arena_kfuncs)\nkernel/bpf/arena.c:1294:BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c-1295-BTF_ID_FLAGS(func, bpf_arena_free_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\n--\nkernel/bpf/verifier.c=11706=enum special_kfunc_type {\n--\nkernel/bpf/verifier.c-11770-\tKF_bpf_task_work_schedule_resume,\nkernel/bpf/verifier.c:11771:\tKF_bpf_arena_alloc_pages,\nkernel/bpf/verifier.c-11772-\tKF_bpf_arena_free_pages,\n--\nkernel/bpf/verifier.c=11860=BTF_ID(func, bpf_task_work_schedule_resume)\nkernel/bpf/verifier.c:11861:BTF_ID(func, bpf_arena_alloc_pages)\nkernel/bpf/verifier.c-11862-BTF_ID(func, bpf_arena_free_pages)\n--\nkernel/bpf/verifier.c=20803=static int specialize_kfunc(struct bpf_verifier_env *env, struct bpf_kfunc_desc *desc, int insn_idx)\n--\nkernel/bpf/verifier.c-20840-\t\t\taddr = (unsigned long)bpf_dynptr_from_file_sleepable;\nkernel/bpf/verifier.c:20841:\t} else if (func_id == special_kfunc_list[KF_bpf_arena_alloc_pages]) {\nkernel/bpf/verifier.c-20842-\t\tif (env-\u003einsn_aux_data[insn_idx].non_sleepable)\nkernel/bpf/verifier.c:20843:\t\t\taddr = (unsigned long)bpf_arena_alloc_pages_non_sleepable;\nkernel/bpf/verifier.c-20844-\t} else if (func_id == special_kfunc_list[KF_bpf_arena_free_pages]) {\n"
}

17/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21717
							output: 29
							thoughts: 31
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach understanding the arena map:

**Digging into the Arena Map's Implementation**

Alright, my immediate thought is to trace the arena map's creation and usage. I need to understand its lifecycle from the ground up. The first point of inquiry is always where the structure gets allocated. So, I see that `arena_map_alloc` is registered within `bpf_map_types`. That's the entry point - this gives me the critical information that this is how the allocation happens, the function that creates the map. Now I can start building a mental model of how this allocation, and subsequent usage, works. This is the seed. I'll need to follow the references from here.



18/2 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "arena_map_ops",
  "PathPrefix": "kernel/bpf"
}

Results:
{
  "Output": "kernel/bpf/arena.c=689=BTF_ID_LIST_SINGLE(bpf_arena_map_btf_ids, struct, bpf_arena)\nkernel/bpf/arena.c:690:const struct bpf_map_ops arena_map_ops = {\nkernel/bpf/arena.c-691-\t.map_meta_equal = bpf_map_meta_equal,\n--\nkernel/bpf/core.c=3413=u64 bpf_check_timed_may_goto(struct bpf_timed_may_goto *p)\n--\nkernel/bpf/core.c-3431-/* for configs without MMU or 32-bit */\nkernel/bpf/core.c:3432:__weak const struct bpf_map_ops arena_map_ops;\nkernel/bpf/core.c-3433-__weak u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)\n"
}

19/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22198
							output: 40
							thoughts: 513
						

Thoughts:
**My Analysis of the BPF Arena Implementation**

Okay, so I'm looking at how the arena map type works within the BPF subsystem, specifically focusing on memory allocation and management. From what I can see, the `arena_map_ops` structure plays a crucial role, providing function pointers for key operations. It's essentially the interface for the arena map type, with `.map_alloc` pointing to `arena_map_alloc`, `.map_mmap` to `arena_map_mmap`, and so on.

The user initiates this process by calling `bpf(BPF_MAP_CREATE, &attr)` with `BPF_MAP_TYPE_ARENA`. This triggers `arena_map_alloc`, which is the starting point for the allocation of arena memory. Then, the user calls `mmap(..., fd)` on the file descriptor associated with the map. This call then invokes `arena_map_mmap`, handling the mapping of the arena's memory region into userspace. When a page fault occurs within the userspace memory region mapped by the arena, `arena_vm_fault` gets called, likely dealing with the actual physical page allocation and management. In addition, BPF programs can directly interact with the arena via `bpf_arena_alloc_pages` and `bpf_arena_free_pages`, or kfuncs.

To understand the core implementation, I'm specifically scrutinizing the changes within `kernel/bpf/arena.c` and `kernel/bpf/range_tree.c`. Let me break down the critical functions.

Within `kernel/bpf/arena.c`, I'm seeing:
*   `arena_vm_fault`: This is likely the page fault handler and is integrated via the `arena_vm_ops` structure.
*   `arena_map_alloc`:  Handles the initial arena map allocation and is part of the `arena_map_ops` set.
*   `arena_map_mmap`: Manages the `mmap` operation, mapping the arena to user space, and is also part of the `arena_map_ops`.
*   `arena_alloc_pages` and `arena_free_pages`:  These are the core page allocators/deallocators, called either directly by the BPF helper functions or via kfuncs.
*   `arena_free_worker`: It seems to be a worker function within a workqueue, suggesting asynchronous handling of memory freeing.

Now, focusing on `kernel/bpf/range_tree.c`, which must be fundamental to the arena's memory management:
*   `range_node_mark_available`, `range_tree_set_avail`, `range_tree_set_unavail`, `range_tree_remove_unavail`, `range_tree_make_avail`, `range_tree_clear` and `is_range_tree_set`: These non-static functions appear to be the externally-visible interfaces for manipulating the range tree.
*   `__find_range`: A static inline, and likely helps find ranges within the tree.
*   `range_tree_set`, `range_tree_merge`: These static functions suggest they're the internal core logic of the range tree operations.

In essence, the range tree seems to be the crucial data structure for tracking available and allocated regions within the arena. It seems that the public functions on this interface allow BPF programs or the kernel to interact with the range tree for the purposes of allocation, deallocation and availability checking.

I need to dig deeper into the interactions between `arena.c` and `range_tree.c` to fully grasp the memory allocation and deallocation strategy. Specifically, I'll pay attention to the exported (non-static) functions in arena.c to understand how different components interact.



20/2 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*\\(.*\\)",
  "PathPrefix": "kernel/bpf/arena.c"
}

Results:
{
  "Output": "kernel/bpf/arena.c-48-\nkernel/bpf/arena.c:49:static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable);\nkernel/bpf/arena.c-50-\nkernel/bpf/arena.c=51=struct bpf_arena {\n--\nkernel/bpf/arena.c-71-\nkernel/bpf/arena.c:72:static void arena_free_worker(struct work_struct *work);\nkernel/bpf/arena.c:73:static void arena_free_irq(struct irq_work *iw);\nkernel/bpf/arena.c-74-\n--\nkernel/bpf/arena.c=82=struct arena_free_span {\n--\nkernel/bpf/arena.c-88-\nkernel/bpf/arena.c:89:u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)\nkernel/bpf/arena.c-90-{\n--\nkernel/bpf/arena.c-93-\nkernel/bpf/arena.c:94:u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)\nkernel/bpf/arena.c-95-{\n--\nkernel/bpf/arena.c-104- */\nkernel/bpf/arena.c:105:u64 bpf_arena_map_kern_vm_start(struct bpf_map *map)\nkernel/bpf/arena.c-106-{\n--\nkernel/bpf/arena.c-117- */\nkernel/bpf/arena.c:118:struct bpf_map *bpf_prog_arena(struct bpf_prog *prog)\nkernel/bpf/arena.c-119-{\n--\nkernel/bpf/arena.c-124-\nkernel/bpf/arena.c:125:static long arena_map_peek_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-126-{\n--\nkernel/bpf/arena.c-129-\nkernel/bpf/arena.c:130:static long arena_map_push_elem(struct bpf_map *map, void *value, u64 flags)\nkernel/bpf/arena.c-131-{\n--\nkernel/bpf/arena.c-134-\nkernel/bpf/arena.c:135:static long arena_map_pop_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-136-{\n--\nkernel/bpf/arena.c-139-\nkernel/bpf/arena.c:140:static long arena_map_delete_elem(struct bpf_map *map, void *value)\nkernel/bpf/arena.c-141-{\n--\nkernel/bpf/arena.c-144-\nkernel/bpf/arena.c:145:static int arena_map_get_next_key(struct bpf_map *map, void *key, void *next_key)\nkernel/bpf/arena.c-146-{\n--\nkernel/bpf/arena.c-149-\nkernel/bpf/arena.c:150:static long compute_pgoff(struct bpf_arena *arena, long uaddr)\nkernel/bpf/arena.c-151-{\n--\nkernel/bpf/arena.c=161=struct clear_range_data {\n--\nkernel/bpf/arena.c-165-\nkernel/bpf/arena.c:166:static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)\nkernel/bpf/arena.c-167-{\n--\nkernel/bpf/arena.c-212-\nkernel/bpf/arena.c:213:static void flush_vmap_cache(unsigned long start, unsigned long size)\nkernel/bpf/arena.c-214-{\n--\nkernel/bpf/arena.c-217-\nkernel/bpf/arena.c:218:static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *data)\nkernel/bpf/arena.c-219-{\n--\nkernel/bpf/arena.c-248-\nkernel/bpf/arena.c:249:static int apply_range_set_scratch_cb(pte_t *pte, unsigned long addr, void *data)\nkernel/bpf/arena.c-250-{\n--\nkernel/bpf/arena.c-266-\nkernel/bpf/arena.c:267:static int populate_pgtable_except_pte(struct bpf_arena *arena)\nkernel/bpf/arena.c-268-{\n--\nkernel/bpf/arena.c-273-\nkernel/bpf/arena.c:274:static struct bpf_map *arena_map_alloc(union bpf_attr *attr)\nkernel/bpf/arena.c-275-{\n--\nkernel/bpf/arena.c-350-\nkernel/bpf/arena.c:351:static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)\nkernel/bpf/arena.c-352-{\n--\nkernel/bpf/arena.c-377-\nkernel/bpf/arena.c:378:static void arena_map_free(struct bpf_map *map)\nkernel/bpf/arena.c-379-{\n--\nkernel/bpf/arena.c-408-\nkernel/bpf/arena.c:409:static void *arena_map_lookup_elem(struct bpf_map *map, void *key)\nkernel/bpf/arena.c-410-{\n--\nkernel/bpf/arena.c=420=static int arena_map_check_btf(struct bpf_map *map, const struct btf *btf,\n--\nkernel/bpf/arena.c-425-\nkernel/bpf/arena.c:426:static u64 arena_map_mem_usage(const struct bpf_map *map)\nkernel/bpf/arena.c-427-{\n--\nkernel/bpf/arena.c=433=struct vma_list {\n--\nkernel/bpf/arena.c-439-\nkernel/bpf/arena.c:440:static int remember_vma(struct bpf_arena *arena, struct vm_area_struct *vma)\nkernel/bpf/arena.c-441-{\n--\nkernel/bpf/arena.c-454-\nkernel/bpf/arena.c:455:static void arena_vm_open(struct vm_area_struct *vma)\nkernel/bpf/arena.c-456-{\n--\nkernel/bpf/arena.c-461-\nkernel/bpf/arena.c:462:static int arena_vm_may_split(struct vm_area_struct *vma, unsigned long addr)\nkernel/bpf/arena.c-463-{\n--\nkernel/bpf/arena.c-466-\nkernel/bpf/arena.c:467:static int arena_vm_mremap(struct vm_area_struct *vma)\nkernel/bpf/arena.c-468-{\n--\nkernel/bpf/arena.c-471-\nkernel/bpf/arena.c:472:static void arena_vm_close(struct vm_area_struct *vma)\nkernel/bpf/arena.c-473-{\n--\nkernel/bpf/arena.c-486-\nkernel/bpf/arena.c:487:static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\nkernel/bpf/arena.c-488-{\n--\nkernel/bpf/arena.c=605=static unsigned long arena_get_unmapped_area(struct file *filp, unsigned long addr,\n--\nkernel/bpf/arena.c-638-\nkernel/bpf/arena.c:639:static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)\nkernel/bpf/arena.c-640-{\n--\nkernel/bpf/arena.c-678-\nkernel/bpf/arena.c:679:static int arena_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)\nkernel/bpf/arena.c-680-{\n--\nkernel/bpf/arena.c-688-\nkernel/bpf/arena.c:689:BTF_ID_LIST_SINGLE(bpf_arena_map_btf_ids, struct, bpf_arena)\nkernel/bpf/arena.c-690-const struct bpf_map_ops arena_map_ops = {\n--\nkernel/bpf/arena.c-708-\nkernel/bpf/arena.c:709:static u64 clear_lo32(u64 val)\nkernel/bpf/arena.c-710-{\n--\nkernel/bpf/arena.c=718=static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt, int node_id,\n--\nkernel/bpf/arena.c-838- */\nkernel/bpf/arena.c:839:static void zap_pages(struct bpf_arena *arena, long uaddr, long page_cnt)\nkernel/bpf/arena.c-840-{\n--\nkernel/bpf/arena.c-896-\nkernel/bpf/arena.c:897:static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)\nkernel/bpf/arena.c-898-{\n--\nkernel/bpf/arena.c-1030- */\nkernel/bpf/arena.c:1031:static int arena_reserve_pages(struct bpf_arena *arena, long uaddr, u32 page_cnt)\nkernel/bpf/arena.c-1032-{\n--\nkernel/bpf/arena.c-1064-\nkernel/bpf/arena.c:1065:static void arena_free_worker(struct work_struct *work)\nkernel/bpf/arena.c-1066-{\n--\nkernel/bpf/arena.c-1212-\nkernel/bpf/arena.c:1213:static void arena_free_irq(struct irq_work *iw)\nkernel/bpf/arena.c-1214-{\n--\nkernel/bpf/arena.c-1219-\nkernel/bpf/arena.c:1220:__bpf_kfunc_start_defs();\nkernel/bpf/arena.c-1221-\n--\nkernel/bpf/arena.c=1246=void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,\n--\nkernel/bpf/arena.c-1257-\nkernel/bpf/arena.c:1258:__bpf_kfunc void bpf_arena_free_pages(void *p__map, void *ptr__ign, u32 page_cnt)\nkernel/bpf/arena.c-1259-{\n--\nkernel/bpf/arena.c-1267-\nkernel/bpf/arena.c:1268:void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt)\nkernel/bpf/arena.c-1269-{\n--\nkernel/bpf/arena.c-1277-\nkernel/bpf/arena.c:1278:__bpf_kfunc int bpf_arena_reserve_pages(void *p__map, void *ptr__ign, u32 page_cnt)\nkernel/bpf/arena.c-1279-{\n--\nkernel/bpf/arena.c-1290-}\nkernel/bpf/arena.c:1291:__bpf_kfunc_end_defs();\nkernel/bpf/arena.c-1292-\nkernel/bpf/arena.c:1293:BTF_KFUNCS_START(arena_kfuncs)\nkernel/bpf/arena.c:1294:BTF_ID_FLAGS(func, bpf_arena_alloc_pages, KF_ARENA_RET | KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c:1295:BTF_ID_FLAGS(func, bpf_arena_free_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c:1296:BTF_ID_FLAGS(func, bpf_arena_reserve_pages, KF_ARENA_ARG2 | KF_SPINLOCK_SAFE)\nkernel/bpf/arena.c:1297:BTF_KFUNCS_END(arena_kfuncs)\nkernel/bpf/arena.c-1298-\nkernel/bpf/arena.c=1299=static const struct btf_kfunc_id_set common_kfunc_set = {\n--\nkernel/bpf/arena.c-1303-\nkernel/bpf/arena.c:1304:static int __init kfunc_init(void)\nkernel/bpf/arena.c-1305-{\n--\nkernel/bpf/arena.c-1307-}\nkernel/bpf/arena.c:1308:late_initcall(kfunc_init);\nkernel/bpf/arena.c-1309-\nkernel/bpf/arena.c=1310=static void __bpf_prog_report_arena_violation(struct bpf_prog *prog, bool write,\n--\nkernel/bpf/arena.c-1328-\nkernel/bpf/arena.c:1329:bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write, unsigned long fault_ip)\nkernel/bpf/arena.c-1330-{\n--\nkernel/bpf/arena.c-1361-\nkernel/bpf/arena.c:1362:void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned long fault_ip)\nkernel/bpf/arena.c-1363-{\n"
}

21/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27276
							output: 30
							thoughts: 1685
						

Thoughts:
**Focus Symbol Identification for BPF Arena and Range Tree Changes**

Okay, let's break this down. My goal here is to identify the most effective `FocusSymbols` for syzkaller to target, given the modifications to `arena.c` and `range_tree.c`. I need to balance specific feature logic with avoiding overly generic, ubiquitous functions.

First, I see the modified functions in `arena.c`: `arena_vm_fault`, `arena_alloc_pages`, `arena_free_pages`, `arena_free_worker`, `bpf_arena_alloc_pages`, `bpf_arena_free_pages`, `arena_map_alloc`, and `arena_map_mmap`. The patch also introduces changes to `range_tree.c`: `range_tree_set_avail`, `range_tree_set_unavail`, `range_node_mark_available`, `range_tree_remove_unavail`, `range_tree_make_avail`, `range_tree_clear`, `range_tree_merge`, `range_get_adjacent`, and `is_range_tree_set`.

The crucial question is which of these can be directly targeted by syzkaller's symbol coverage filters. Remember that static functions within `.c` files *usually* have symbols in `vmlinux`, unless inlined. I need to rule out the static inlines.

`arena_vm_fault`, `arena_free_worker`, `arena_map_alloc`, and `arena_map_mmap` are good candidates because their addresses are taken, guaranteeing their presence in `vmlinux`. `arena_alloc_pages` and `arena_free_pages` are called from multiple places, they are also good focus candidates. The public kfuncs, `bpf_arena_alloc_pages` and `bpf_arena_free_pages` are also prime targets.

Now, let's evaluate the `range_tree.c` functions. I've got a mix of new and modified functions here. `range_tree_set_avail`, `range_tree_set_unavail`, `range_tree_remove_unavail`, `range_tree_make_avail`, `range_tree_clear`, `range_node_mark_available` and `is_range_tree_set` are all promising.

Based on the rules of FocusSymbols, I need to avoid listing generic functions.

`arena_vm_fault` is good because it's specific to the bpf_arena maps (not a generic page fault handler) and is highly related to the VM operations.

Considering all this, my proposed `FocusSymbols` list is:

*   `arena_vm_fault`
*   `arena_alloc_pages`
*   `arena_free_pages`
*   `arena_free_worker`
*   `range_tree_clear`
*   `range_tree_set_avail`
*   `range_tree_set_unavail`
*   `range_tree_remove_unavail`
*   `range_tree_make_avail`
*   `range_node_mark_available`
*   `bpf_arena_alloc_pages`
*   `bpf_arena_free_pages`

I also see that some of the range tree functions are static, namely: `range_tree_set`, `range_tree_merge`, and `range_get_adjacent`. Since they're static and not directly called from outside the module, I will not include them on the list.

Finally, I need to make sure the relevant configuration options are enabled for these symbols to be valid. The makefile check confirms that `CONFIG_BPF_SYSCALL` with MMU and 64-bit support enables the relevant object files. I'll double-check `arena.c` and `range_tree.c` for any other conditional compilation, but this seems like the main driver. This seems all in order.



22/2 2026/09/25 11:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "CONFIG_",
  "PathPrefix": "kernel/bpf/arena.c"
}

Results:
{
  "error": "no matches"
}

23/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29873
							output: 30
							thoughts: 37
						

Thoughts:
Here's my summary, as the expert:

**Investigating BPF Arena Configuration**

Alright, something seems off. I'm seeing no traces of any `CONFIG_` preprocessor directives within the `kernel/bpf/arena.c` file. That immediately raises a flag. We're dealing with BPF, which means we *should* be looking at conditional compilation. So, the first logical step is to check the related range tree code. I'll need to scrutinize both `range_tree.c` and `range_tree.h` to see if the configuration flags are being used there, if the arena is used there, or if I'm missing something fundamental about the relationship between these components. We need to understand how the arena and range tree interact, and where the conditional compilation is actually taking place.



24/2 2026/09/25 11:39 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "CONFIG_",
  "PathPrefix": "kernel/bpf/range_tree"
}

Results:
{
  "error": "no matches"
}

25/2 2026/09/25 11:39 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30129
							output: 265
							thoughts: 442
						

Thoughts:
**Fuzzing Strategy Decision**

Alright, let's get down to brass tacks. There weren't any immediate red flags in the initial assessment, so the path forward is clear: `BPF_SYSCALL` is the crucial configuration flag we need.

Now, let's solidify the reasoning for fuzzing. The patch is deeply intertwined with the BPF arena memory management and range tree tracking within the kernel. It modifies core functions like `arena_vm_fault`, `arena_free_pages`, and `arena_free_worker`, introducing a novel unavailable state for range tree nodes to avoid race conditions during page operations. There are new defensive checks with `WARN_ON_ONCE` statements as well, which are always good indicators of potential fuzz targets.

Crucially, this code is completely reachable. We can trigger it through the standard `bpf()` syscall, specifically `BPF_MAP_CREATE` with `BPF_MAP_TYPE_ARENA`, as well as `mmap()`, page faults, and BPF arena kfuncs, all within a standard QEMU amd64 environment. No special setup is required. The exploitability is also quite high due to the nature of the changes and the concurrency issues.

Everything checks out, no gotchas. So, let's prepare the call to `set-results`. We set `WorthFuzzing` to `true`, provide the concise explanation as `Reasoning`, and specify `BPF_SYSCALL` under `EnableConfigs`. We need to define the symbols: `arena_vm_fault`, `arena_alloc_pages`, `arena_free_pages`, `arena_free_worker`, `range_tree_set_avail`, `range_tree_set_unavail`, `range_tree_remove_unavail`, `range_tree_make_avail`, `range_tree_clear`, `bpf_arena_alloc_pages`, and `bpf_arena_free_pages`. These are the key functions. With all that set, we'll invoke `set-results`.



26/2 2026/09/25 11:39 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "arena_vm_fault",
    "arena_alloc_pages",
    "arena_free_pages",
    "arena_free_worker",
    "range_tree_set_avail",
    "range_tree_set_unavail",
    "range_tree_remove_unavail",
    "range_tree_make_avail",
    "range_tree_clear",
    "bpf_arena_alloc_pages",
    "bpf_arena_free_pages"
  ],
  "Reasoning": "The patch refactors BPF arena memory management and range tree handling to prevent races between concurrent arena page allocation/faulting and freeing/TLB flushing. It modifies arena page fault handling (arena_vm_fault), freeing workflows (arena_free_pages, arena_free_worker), and introduces range node availability tracking in the range tree, along with multiple new WARN_ON_ONCE assertions. This code is reachable from user space on amd64 via BPF system calls (creating BPF_MAP_TYPE_ARENA maps), mmap(), page faults, and arena kfuncs.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BPF_SYSCALL"
  ],
  "FocusSymbols": [
    "arena_vm_fault",
    "arena_alloc_pages",
    "arena_free_pages",
    "arena_free_worker",
    "range_tree_set_avail",
    "range_tree_set_unavail",
    "range_tree_remove_unavail",
    "range_tree_make_avail",
    "range_tree_clear",
    "bpf_arena_alloc_pages",
    "bpf_arena_free_pages"
  ],
  "Reasoning": "The patch refactors BPF arena memory management and range tree handling to prevent races between concurrent arena page allocation/faulting and freeing/TLB flushing. It modifies arena page fault handling (arena_vm_fault), freeing workflows (arena_free_pages, arena_free_worker), and introduces range node availability tracking in the range tree, along with multiple new WARN_ON_ONCE assertions. This code is reachable from user space on amd64 via BPF system calls (creating BPF_MAP_TYPE_ARENA maps), mmap(), page faults, and arena kfuncs.",
  "WorthFuzzing": true
}

27/1 2026/09/25 11:39 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

28/2 2026/09/25 11:39 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch addresses race conditions and synchronization issues in the BPF arena subsystem during page faulting, reallocation, and freeing (ensuring ranges remain marked unavailable until TLB flush, PTE unmapping, and page freeing are complete).\n\nThe changes introduce state tracking in `struct arena_free_span` and an `available` flag in `struct range_node`, alongside managing transient unavailable ranges in the arena range tree (`arena-\u003ert`). \n\nFrom a memory sanitization perspective:\n1. No uninitialized memory is read or exposed: All newly added and modified heap allocations (`struct arena_free_span` and `struct range_node`) have all of their fields explicitly initialized immediately after allocation before any read access or insertion into data structures.\n2. No kernel data structures, buffers, or uninitialized padding are copied to userspace (no `copy_to_user`, `put_user`, socket options, etc.).\n3. The potential risks associated with these concurrency and synchronization modifications involve race conditions, use-after-free, double-free, and lock contention/deadlocks. These bug categories are detected by KASAN, LOCKDEP, and MM sanity checks in standard KASAN kernel builds.\n\nSince there are no uninitialized memory read risks or info-leaks, spawning a dedicated KMSAN fuzzing session is not warranted.",
  "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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 9a0e7021e2025ed16d20a20f6bcbe33bb7e16186
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 25 11:38:45 2026 +0000

    syz-cluster: applied patch under review

diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b431..299525f93bbda 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -72,10 +72,18 @@ struct bpf_arena {
 static void arena_free_worker(struct work_struct *work);
 static void arena_free_irq(struct irq_work *iw);
 
+enum arena_free_span_state {
+	ARENA_FREE_SPAN_NOT_STARTED,	/* Freeing not started */
+	ARENA_FREE_SPAN_UNAVAIL,	/* Region cleared & unavailable */
+	ARENA_FREE_SPAN_ZAPPED,		/* Region zapped and cleared */
+	ARENA_FREE_SPAN_FAILED,		/* Freeing operation cannot continue */
+};
+
 struct arena_free_span {
 	struct llist_node node;
 	unsigned long uaddr;
 	u32 page_cnt;
+	enum arena_free_span_state state;
 };
 
 u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)
@@ -317,7 +325,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
 		goto err_free_arena;
 
 	range_tree_init(&arena->rt);
-	err = range_tree_set(&arena->rt, 0, attr->max_entries);
+	err = range_tree_set_avail(&arena->rt, 0, attr->max_entries);
 	if (err)
 		goto err_free_scratch;
 	mutex_init(&arena->lock);
@@ -481,21 +489,22 @@ 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 range_node *unavail_node;
 	struct page *page;
 	long kbase, kaddr;
 	unsigned long flags;
+	vm_fault_t ret_fault;
 	int ret;
 
 	kbase = bpf_arena_get_kern_vm_start(arena);
 	kaddr = kbase + (u32)(vmf->address);
 
-	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.
-		 */
+	ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags);
+	/* If we are deadlocking somehow, no way to ensure forward progress. */
+	if (ret == -EDEADLK)
 		return VM_FAULT_SIGBUS;
+	if (ret)
+		goto retry;
 
 	page = vmalloc_to_page((void *)kaddr);
 	if (page) {
@@ -513,6 +522,14 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
 		goto out_sigsegv_memcg;
 
 	ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
+	/* If a range is unavailable, try again. */
+	if (ret == -EAGAIN) {
+		raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+		bpf_map_memcg_exit(old_memcg, new_memcg);
+
+		goto retry;
+	}
+
 	if (ret)
 		goto out_sigsegv_memcg;
 
@@ -520,28 +537,61 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
 	/* 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);
+		range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
 		goto out_sigsegv_memcg;
 	}
 
 	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);
+		range_tree_set_avail(&arena->rt, vmf->pgoff, 1);
 		free_pages_nolock(page, 0);
 		goto out_sigsegv_memcg;
 	}
 	flush_vmap_cache(kaddr, PAGE_SIZE);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 out:
-	page_ref_add(page, 1);
+	/* Reserve the page while installing its user PTE without the arena lock. */
+	bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
+	unavail_node = range_tree_set_unavail(&arena->rt, vmf->pgoff, 1);
+	bpf_map_memcg_exit(old_memcg, new_memcg);
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
-	vmf->page = page;
-	return 0;
+	if (IS_ERR(unavail_node)) {
+		ret = PTR_ERR(unavail_node);
+		if (ret == -EAGAIN)
+			goto retry;
+		return VM_FAULT_OOM;
+	}
+
+	ret_fault = vmf_insert_page(vmf->vma, vmf->address, page);
+
+	while ((ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags))) {
+		/* If we somehow deadlocked stop trying to take the lock. */
+		if (ret == -EDEADLK)
+			return VM_FAULT_SIGBUS;
+
+		cond_resched();
+	}
+
+	ret = range_tree_remove_unavail(&arena->rt, vmf->pgoff, 1);
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+	WARN_ON_ONCE(ret);
+	return ret_fault;
 out_sigsegv_memcg:
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 out_sigsegv:
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 	return VM_FAULT_SIGSEGV;
+
+retry:
+
+	/* Only for special cases (GUP/device drivers). */
+	if (!(vmf->flags & FAULT_FLAG_ALLOW_RETRY))
+		return VM_FAULT_SIGBUS;
+
+	if (!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
+		release_fault_lock(vmf);
+
+	return VM_FAULT_RETRY;
 }
 
 static const struct vm_operations_struct arena_vm_ops = {
@@ -621,7 +671,7 @@ static int arena_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
 	 * of user_vm_start. Set VM_DONTCOPY to prevent arena VMA from
 	 * being copied into the child process on fork.
 	 */
-	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY);
+	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTCOPY | VM_MIXEDMAP);
 	vma->vm_ops = &arena_vm_ops;
 	return 0;
 }
@@ -766,7 +816,7 @@ static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 	return clear_lo32(arena->user_vm_start) + uaddr32;
 out:
-	range_tree_set(&arena->rt, pgoff + mapped, page_cnt - mapped);
+	range_tree_set_avail(&arena->rt, pgoff + mapped, page_cnt - mapped);
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 	if (mapped) {
 		flush_vmap_cache(kern_vm_start + uaddr32, mapped << PAGE_SHIFT);
@@ -847,6 +897,7 @@ static void zap_pages(struct bpf_arena *arena, long uaddr, long page_cnt)
 static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt, bool sleepable)
 {
 	struct mem_cgroup *new_memcg, *old_memcg;
+	struct range_node *unavail_node = NULL;
 	u64 full_uaddr, uaddr_end;
 	long kaddr, pgoff;
 	struct page *page;
@@ -855,6 +906,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 	struct arena_free_span *s;
 	struct clear_range_data cdata;
 	unsigned long flags;
+	bool release_only = false;
 	int ret = 0;
 
 	/* only aligned lower 32-bit are relevant */
@@ -881,7 +933,20 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 	if (ret)
 		goto defer;
 
-	range_tree_set(&arena->rt, pgoff, page_cnt);
+	unavail_node = range_tree_set_unavail(&arena->rt, pgoff, page_cnt);
+	if (IS_ERR(unavail_node)) {
+		ret = PTR_ERR(unavail_node);
+		raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+		/*
+		 * For -EAGAIN: An overlapping fault reserves
+		 * the range before installing its PTE.
+		 */
+		if (ret == -ENOMEM || ret == -EAGAIN)
+			goto defer;
+		WARN_ON_ONCE(ret);
+		bpf_map_memcg_exit(old_memcg, new_memcg);
+		return;
+	}
 
 	init_llist_head(&free_pages);
 	cdata.arena = arena;
@@ -911,6 +976,16 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 			zap_pages(arena, full_uaddr, 1);
 		__free_page(page);
 	}
+
+	ret = raw_res_spin_lock_irqsave(&arena->spinlock, flags);
+	if (ret) {
+		release_only = true;
+		goto defer;
+	}
+
+	ret = range_tree_make_avail(&arena->rt, pgoff, page_cnt);
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+	WARN_ON_ONCE(ret);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
 
 	return;
@@ -918,16 +993,33 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
 defer:
 	s = kmalloc_nolock(sizeof(struct arena_free_span), __GFP_ACCOUNT, -1);
 	bpf_map_memcg_exit(old_memcg, new_memcg);
-	if (!s)
+	if (!s) {
 		/*
-		 * If allocation fails in non-sleepable context, pages are intentionally left
-		 * inaccessible (leaked) until the arena is destroyed. Cleanup or retries are not
-		 * possible here, so we intentionally omit them for safety.
+		 * Directly mark the region available. Unavailable regions must
+		 * always be transient, and a permanent one breaks the assumptions
+		 * made in the allocation/faulting code. The operation is safe because
+		 * unavailable nodes are not modified by the range tree code without a reference
+		 * to the node. The modification from unavailable to available also does
+		 * not require touching anything in the tree apart from the node itself,
+		 * so it can be done locklessly. The downside is fragmentation in the tree,
+		 * since we cannot merge with neighbors, but this is a) safe and b) better
+		 * than leaking the range.
 		 */
+		if (release_only)
+			range_node_mark_available(unavail_node);
+
+		/*
+		 * If we haven't even zapped the pages, intentionally leave them
+		 * inaccessible (leaked) until the arena is destroyed. Cleanup or
+		 * retries are not possible here, so we intentionally omit them for safety.
+		 */
+
 		return;
+	}
 
 	s->page_cnt = page_cnt;
 	s->uaddr = uaddr;
+	s->state = release_only ? ARENA_FREE_SPAN_ZAPPED : ARENA_FREE_SPAN_NOT_STARTED;
 	llist_add(&s->node, &arena->free_spans);
 	irq_work_queue(&arena->free_irq);
 }
@@ -976,6 +1068,7 @@ static void arena_free_worker(struct work_struct *work)
 	struct mem_cgroup *new_memcg, *old_memcg;
 	struct llist_node *list, *pos, *t;
 	struct arena_free_span *s;
+	struct range_node *unavail_node;
 	u64 arena_vm_start, user_vm_start;
 	struct llist_head free_pages;
 	struct clear_range_data cdata;
@@ -983,6 +1076,8 @@ static void arena_free_worker(struct work_struct *work)
 	unsigned long full_uaddr;
 	long kaddr, page_cnt, pgoff;
 	unsigned long flags;
+	bool retry = false;
+	int ret;
 
 	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
 		schedule_work(work);
@@ -998,23 +1093,48 @@ static void arena_free_worker(struct work_struct *work)
 	user_vm_start = bpf_arena_get_user_vm_start(arena);
 
 	list = llist_del_all(&arena->free_spans);
-	llist_for_each(pos, list) {
+	llist_for_each_safe(pos, t, list) {
 		s = llist_entry(pos, struct arena_free_span, node);
+		if (s->state != ARENA_FREE_SPAN_NOT_STARTED)
+			continue;
+
 		page_cnt = s->page_cnt;
-		kaddr = arena_vm_start + s->uaddr;
 		pgoff = compute_pgoff(arena, s->uaddr);
+		kaddr = arena_vm_start + s->uaddr;
+
+		unavail_node = range_tree_set_unavail(&arena->rt, pgoff, page_cnt);
+		if (IS_ERR(unavail_node)) {
+			ret = PTR_ERR(unavail_node);
+			/* Kick off another attempt at the end of this call. */
+			if (ret == -EAGAIN)
+				continue;
+
+			/*
+			 * An -ENOMEM failure is the same failure mode as in
+			 * the defer: path of arena_free_pages(). Do not treat
+			 * the leak as a bug.
+			 */
+			if (ret != -ENOMEM)
+				WARN_ON_ONCE(ret);
+
+			s->state = ARENA_FREE_SPAN_FAILED;
+			continue;
+		}
+
+		s->state = ARENA_FREE_SPAN_UNAVAIL;
 
 		/* clear ptes and collect pages in free_pages llist */
 		apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
 					     apply_range_clear_cb, &cdata);
-
-		range_tree_set(&arena->rt, pgoff, page_cnt);
 	}
 	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
 
-	/* Iterate the list again without holding spinlock to do the tlb flush and zap_pages */
+	/* Keep ranges unavailable until their stale translations are gone. */
 	llist_for_each_safe(pos, t, list) {
 		s = llist_entry(pos, struct arena_free_span, node);
+		if (s->state != ARENA_FREE_SPAN_UNAVAIL)
+			continue;
+
 		page_cnt = s->page_cnt;
 		full_uaddr = clear_lo32(user_vm_start) + s->uaddr;
 		kaddr = arena_vm_start + s->uaddr;
@@ -1025,7 +1145,11 @@ static void arena_free_worker(struct work_struct *work)
 		/* remove pages from user vmas */
 		zap_pages(arena, full_uaddr, page_cnt);
 
-		kfree_nolock(s);
+		/*
+		 * Used to avoid zapping twice if we fail the lock acquisition
+		 * below and rerun the span through this function.
+		 */
+		s->state = ARENA_FREE_SPAN_ZAPPED;
 	}
 
 	/* free all pages collected by apply_to_existing_page_range() in the first loop */
@@ -1034,7 +1158,56 @@ static void arena_free_worker(struct work_struct *work)
 		__free_page(page);
 	}
 
+	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
+		llist_for_each_safe(pos, t, list) {
+			s = llist_entry(pos, struct arena_free_span, node);
+
+			if (s->state == ARENA_FREE_SPAN_FAILED) {
+				kfree_nolock(s);
+				continue;
+			}
+
+			llist_add(pos, &arena->free_spans);
+			retry = true;
+		}
+		goto done;
+	}
+
+	llist_for_each_safe(pos, t, list) {
+		s = llist_entry(pos, struct arena_free_span, node);
+
+		/* Remove the spans of failed allocations. */
+		if (s->state == ARENA_FREE_SPAN_FAILED) {
+			kfree_nolock(s);
+			continue;
+		}
+
+		if (s->state == ARENA_FREE_SPAN_NOT_STARTED) {
+			llist_add(pos, &arena->free_spans);
+			retry = true;
+			continue;
+		}
+
+		page_cnt = s->page_cnt;
+		pgoff = compute_pgoff(arena, s->uaddr);
+		/*
+		 * This range tree operation does not allocate memory,
+		 * and so should never fail regardless of contention
+		 * or memory pressure. This is in contrast to regular
+		 * inserts that _can_ fail under memory pressure and
+		 * force us to defer the free.
+		 */
+		ret = range_tree_make_avail(&arena->rt, pgoff, page_cnt);
+		WARN_ON_ONCE(ret);
+		kfree_nolock(s);
+	}
+	raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+
+done:
 	bpf_map_memcg_exit(old_memcg, new_memcg);
+
+	if (retry)
+		schedule_work(work);
 }
 
 static void arena_free_irq(struct irq_work *iw)
diff --git a/kernel/bpf/range_tree.c b/kernel/bpf/range_tree.c
index 2f28886f3ff75..62ebf4df51cb4 100644
--- a/kernel/bpf/range_tree.c
+++ b/kernel/bpf/range_tree.c
@@ -3,6 +3,7 @@
 #include <linux/interval_tree_generic.h>
 #include <linux/slab.h>
 #include <linux/bpf.h>
+#include <linux/err.h>
 #include "range_tree.h"
 
 /*
@@ -39,8 +40,29 @@ struct range_node {
 	u32 rn_start;
 	u32 rn_last; /* inclusive */
 	u32 __rn_subtree_last;
+	bool available; /* range is available for allocating. */
 };
 
+/* Is the range available for merging? */
+static inline bool range_available(struct range_node *rn)
+{
+	/* Pairs with smp_store_release() in range_node_mark_available(). */
+	return rn && smp_load_acquire(&rn->available);
+}
+
+/*
+ * Mark a node as available. Designed to be used locklessly
+ * as a last-ditch effort to avoid leaking unavailable range
+ * tree nodes when all attempts to directly or indirectly
+ * free an arena region has failed. See arena_free_pages()
+ * for more info.
+ */
+void range_node_mark_available(struct range_node *rn)
+{
+	/* Pairs with smp_load_acquire() in range_available(). */
+	smp_store_release(&rn->available, true);
+}
+
 static struct range_node *rb_to_range_node(struct rb_node *rb)
 {
 	return rb_entry(rb, struct range_node, rb_range_size);
@@ -55,20 +77,30 @@ static u32 rn_size(struct range_node *rn)
 static inline struct range_node *__find_range(struct range_tree *rt, u32 len)
 {
 	struct rb_node *rb = rt->range_size_root.rb_root.rb_node;
-	struct range_node *best = NULL;
+	struct rb_node *best = NULL;
+	struct range_node *rn;
 
 	while (rb) {
-		struct range_node *rn = rb_to_range_node(rb);
+		rn = rb_to_range_node(rb);
 
 		if (len <= rn_size(rn)) {
-			best = rn;
+			best = rb;
 			rb = rb->rb_right;
 		} else {
 			rb = rb->rb_left;
 		}
 	}
 
-	return best;
+	/* Filter unavailable ranges. */
+	while (best) {
+		rn = rb_to_range_node(best);
+		if (range_available(rn))
+			return rn;
+
+		best = rb_prev(best);
+	}
+
+	return NULL;
 }
 
 s64 range_tree_find(struct range_tree *rt, u32 len)
@@ -135,10 +167,19 @@ range_it_iter_first(struct range_tree *rt, u32 start, u32 last)
 /* Clear the range in this range tree */
 int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 {
+	u32 first = start;
 	u32 last = start + len - 1;
 	struct range_node *new_rn;
 	struct range_node *rn;
 
+	/* Scan for unavailable ranges and try again if so. */
+	while ((rn = range_it_iter_first(rt, first, last))) {
+		if (!range_available(rn))
+			return -EAGAIN;
+
+		first = rn->rn_last + 1;
+	}
+
 	while ((rn = range_it_iter_first(rt, start, last))) {
 		if (rn->rn_start < start && rn->rn_last > last) {
 			u32 old_last = rn->rn_last;
@@ -153,6 +194,7 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 						NUMA_NO_NODE);
 			if (!new_rn)
 				return -ENOMEM;
+			new_rn->available = rn->available;
 			new_rn->rn_start = last + 1;
 			new_rn->rn_last = old_last;
 			range_it_insert(new_rn, rt);
@@ -180,32 +222,31 @@ int range_tree_clear(struct range_tree *rt, u32 start, u32 len)
 int is_range_tree_set(struct range_tree *rt, u32 start, u32 len)
 {
 	u32 last = start + len - 1;
-	struct range_node *left;
+	struct range_node *rn;
 
-	/* Is this whole range set ? */
-	left = range_it_iter_first(rt, start, last);
-	if (left && left->rn_start <= start && left->rn_last >= last)
-		return 0;
+	for ((rn = range_it_iter_first(rt, start, last)); rn != NULL;
+		rn = __range_it_iter_next(rn, start, last)) {
+		/* Make sure the range covers the start */
+		if (rn->rn_start > start)
+			return -ESRCH;
+
+		/* If it covers the entire range we're done. */
+		if (rn->rn_last >= last)
+			return 0;
+
+		start = rn->rn_last + 1;
+	}
+
+	/* No range to cover [start, last] */
 	return -ESRCH;
 }
 
-/* Set the range in this range tree */
-int range_tree_set(struct range_tree *rt, u32 start, u32 len)
+/* Do we have adjacent ranges (and do not overlap with them)? */
+static int range_get_adjacent(struct range_tree *rt, u32 start, u32 last,
+		struct range_node **leftp, struct range_node **rightp)
 {
-	u32 last = start + len - 1;
 	struct range_node *right;
 	struct range_node *left;
-	int err;
-
-	/* Is this whole range already set ? */
-	left = range_it_iter_first(rt, start, last);
-	if (left && left->rn_start <= start && left->rn_last >= last)
-		return 0;
-
-	/* Clear out everything in the range we want to set. */
-	err = range_tree_clear(rt, start, len);
-	if (err)
-		return err;
 
 	/* Do we have a left-adjacent range ? */
 	left = range_it_iter_first(rt, start - 1, start - 1);
@@ -217,31 +258,163 @@ int range_tree_set(struct range_tree *rt, u32 start, u32 len)
 	if (right && right->rn_start != last + 1)
 		return -EFAULT;
 
-	if (left && right) {
+	*leftp = left;
+	*rightp = right;
+
+	return 0;
+}
+
+/*
+ * Merge with adjacent available ranges if possible. The new [start, last]
+ * has already been confirmed to be adjacent with left/right by the caller.
+ */
+static int range_tree_merge(struct range_tree *rt, u32 start, u32 last,
+		struct range_node *left, struct range_node *right)
+{
+	if (range_available(left) && range_available(right)) {
 		/* Combine left and right adjacent ranges */
 		range_it_remove(left, rt);
 		range_it_remove(right, rt);
 		left->rn_last = right->rn_last;
 		range_it_insert(left, rt);
 		kfree_nolock(right);
-	} else if (left) {
+	} else if (range_available(left)) {
 		/* Combine with the left range */
 		range_it_remove(left, rt);
 		left->rn_last = last;
 		range_it_insert(left, rt);
-	} else if (right) {
+	} else if (range_available(right)) {
 		/* Combine with the right range */
 		range_it_remove(right, rt);
 		right->rn_start = start;
 		range_it_insert(right, rt);
 	} else {
-		left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
-		if (!left)
-			return -ENOMEM;
-		left->rn_start = start;
-		left->rn_last = last;
-		range_it_insert(left, rt);
+		/* No merge available. */
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+/* Make a range available, possibly merging. */
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len)
+{
+	u32 last = start + len - 1;
+	struct range_node *rn;
+	struct range_node *right;
+	struct range_node *left;
+	int err;
+
+	/*
+	 * Confirm the range exists is unavailable,
+	 * and fits the requested range exactly.
+	 */
+	rn = range_it_iter_first(rt, start, last);
+	if (!rn || rn->available)
+		return -EINVAL;
+
+	if (rn->rn_start != start || rn->rn_last != last)
+		return -EINVAL;
+
+	err = range_get_adjacent(rt, start, last, &left, &right);
+	if (err)
+		return err;
+
+	/* If no merging required, just make available. */
+	if (!range_available(left) && !range_available(right)) {
+		rn->available = true;
+		return 0;
 	}
+
+	/* Can merge, remove the range already. */
+	start = rn->rn_start;
+	last = rn->rn_last;
+	range_it_remove(rn, rt);
+	kfree_nolock(rn);
+
+	return range_tree_merge(rt, start, last, left, right);
+}
+
+/* Set the range in this range tree */
+static int range_tree_set(struct range_tree *rt, u32 start, u32 len, bool available,
+			  struct range_node **new_rn)
+{
+	u32 last = start + len - 1;
+	struct range_node *right;
+	struct range_node *left;
+	int err;
+
+	/* Is this whole range already set ? */
+	left = range_it_iter_first(rt, start, last);
+	if (left && left->rn_start <= start && left->rn_last >= last &&
+	    range_available(left) && available)
+		return 0;
+
+	/* Clear out everything in the range we want to set. */
+	err = range_tree_clear(rt, start, len);
+	if (err)
+		return err;
+
+	/* Get adjacent ranges and check for overlaps. */
+	err = range_get_adjacent(rt, start, last, &left, &right);
+	if (err)
+		return err;
+
+	/*
+	 * If the range is not available for allocation, don't merge.
+	 * Unavailable ranges are in the process of being freed and should
+	 * be imminently marked available, so merging them with other
+	 * unavailable ranges will just lead to splitting the range back
+	 * almost immediately.
+	 */
+	if (available) {
+		err = range_tree_merge(rt, start, last, left, right);
+		if (!err)
+			return 0;
+	}
+
+	left = kmalloc_nolock(sizeof(struct range_node), __GFP_ACCOUNT, NUMA_NO_NODE);
+	if (!left)
+		return -ENOMEM;
+	left->available = available;
+	left->rn_start = start;
+	left->rn_last = last;
+	range_it_insert(left, rt);
+	if (new_rn)
+		*new_rn = left;
+
+	return 0;
+}
+
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len)
+{
+	return range_tree_set(rt, start, len, true, NULL);
+}
+
+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len)
+{
+	struct range_node *rn = NULL;
+	int err;
+
+	err = range_tree_set(rt, start, len, false, &rn);
+	if (err)
+		return ERR_PTR(err);
+	if (WARN_ON_ONCE(!rn))
+		return ERR_PTR(-EINVAL);
+	return rn;
+}
+
+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len)
+{
+	u32 last = start + len - 1;
+	struct range_node *rn;
+
+	rn = range_it_iter_first(rt, start, last);
+	if (!rn || rn->available || rn->rn_start != start || rn->rn_last != last)
+		return -EINVAL;
+
+	range_it_remove(rn, rt);
+	kfree_nolock(rn);
 	return 0;
 }
 
diff --git a/kernel/bpf/range_tree.h b/kernel/bpf/range_tree.h
index ff0b9110eb715..79295abe36845 100644
--- a/kernel/bpf/range_tree.h
+++ b/kernel/bpf/range_tree.h
@@ -10,11 +10,17 @@ struct range_tree {
 	struct rb_root_cached range_size_root;
 };
 
+struct range_node;
+
 void range_tree_init(struct range_tree *rt);
 void range_tree_destroy(struct range_tree *rt);
 
 int range_tree_clear(struct range_tree *rt, u32 start, u32 len);
-int range_tree_set(struct range_tree *rt, u32 start, u32 len);
+int range_tree_set_avail(struct range_tree *rt, u32 start, u32 len);
+struct range_node *range_tree_set_unavail(struct range_tree *rt, u32 start, u32 len);
+void range_node_mark_available(struct range_node *rn);
+int range_tree_remove_unavail(struct range_tree *rt, u32 start, u32 len);
+int range_tree_make_avail(struct range_tree *rt, u32 start, u32 len);
 int is_range_tree_set(struct range_tree *rt, u32 start, u32 len);
 s64 range_tree_find(struct range_tree *rt, u32 len);
 
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_race.c b/tools/testing/selftests/bpf/prog_tests/arena_race.c
new file mode 100644
index 0000000000000..c2da611421f3a
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_race.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/syscall.h>
+#include <test_progs.h>
+
+#include "arena_race.skel.h"
+
+struct free_thread_ctx {
+	struct arena_race *skel;
+	int err;
+	__u32 retval;
+};
+
+struct fault_thread_ctx {
+	__u64 *addr;
+	int stop;
+};
+
+static int run_prog(struct bpf_program *prog, const char *name)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	int err;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(prog), &opts);
+	return ASSERT_OK(err, name) && ASSERT_OK(opts.retval, name) ? 0 : -1;
+}
+
+/* Trigger the sleepable free page path. */
+static void *run_free_thread(void *arg)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, opts);
+	struct free_thread_ctx *ctx = arg;
+
+	ctx->skel->bss->target_tid = sys_gettid();
+	ctx->err = bpf_prog_test_run_opts(
+		bpf_program__fd(ctx->skel->progs.free_page), &opts);
+	ctx->retval = opts.retval;
+	return NULL;
+}
+
+/* Continuously fault in the address. */
+static void *fault_reader_thread(void *arg)
+{
+	struct fault_thread_ctx *ctx = arg;
+
+	while (!READ_ONCE(ctx->stop))
+		(void)READ_ONCE(*ctx->addr);
+	return NULL;
+}
+
+static int wait_for(int *p)
+{
+	__u64 deadline = get_time_ns() + 5ULL * 1000 * 1000 * 1000;
+
+	while (!READ_ONCE(*p)) {
+		if (get_time_ns() > deadline)
+			return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+static struct arena_race *setup_arena(__u64 **addr, bool trace_flush)
+{
+	struct arena_race *skel;
+	size_t arena_sz;
+	char *base;
+	int err;
+
+	skel = arena_race__open();
+	if (!ASSERT_OK_PTR(skel, "open"))
+		return NULL;
+	err = bpf_program__set_autoload(skel->progs.trace_flush, trace_flush);
+	if (!ASSERT_OK(err, "trace_flush_autoload"))
+		goto err_out;
+
+	err = arena_race__load(skel);
+	if (!ASSERT_OK(err, "load"))
+		goto err_out;
+	err = arena_race__attach(skel);
+	if (!ASSERT_OK(err, "attach"))
+		goto err_out;
+
+	if (run_prog(skel->progs.alloc_old, "alloc_old"))
+		goto err_out;
+	if (skel->bss->skip) {
+		test__skip();
+		goto err_out;
+	}
+
+	base = bpf_map__initial_value(skel->maps.arena, &arena_sz);
+	if (!ASSERT_OK_PTR(base, "arena_base"))
+		goto err_out;
+	*addr = (__u64 *)(base + getpagesize());
+	if (!ASSERT_EQ((unsigned long)skel->bss->ptr, (unsigned long)*addr,
+		       "arena_ptr"))
+		goto err_out;
+	return skel;
+
+err_out:
+	arena_race__destroy(skel);
+	return NULL;
+}
+
+static void test_free_before_flush(bool deferred)
+{
+	struct free_thread_ctx ctx = {};
+	struct arena_race *skel;
+	pthread_t thread;
+	__u64 *addr;
+	bool thread_created = false, flush_seen = false, completed = false;
+	int err;
+
+	if (libbpf_find_vmlinux_btf_id("flush_tlb_kernel_range",
+					BPF_TRACE_FENTRY) <= 0) {
+		printf("%s:SKIP: flush_tlb_kernel_range is not an fentry target\n",
+		       __func__);
+		test__skip();
+		return;
+	}
+
+	skel = setup_arena(&addr, true);
+	if (!skel)
+		return;
+
+	/* Pause during a TLB flush to widen the race window. */
+	skel->bss->pause_on_flush = 1;
+
+	if (deferred) {
+		/*
+		 * Test the nonsleepable free path that gets
+		 * deferred to a worker in the kernel. We do
+		 * so by triggering the arena operation from
+		 * a nonsleepable tracepoint context.
+		 */
+		skel->bss->trigger_pid_tgid =
+			((__u64)getpid() << 32) | (__u32)sys_gettid();
+		skel->bss->trigger_syscall = SYS_getpgid;
+		skel->bss->deferred_free = 1;
+		if (!ASSERT_GE(syscall(SYS_getpgid, 0), 0, "deferred_free"))
+			goto release;
+	} else {
+		/*
+		 * Test the sleepable arena free path through a
+		 * syscall test prog.
+		 */
+		ctx.skel = skel;
+		err = pthread_create(&thread, NULL, run_free_thread, &ctx);
+		if (!ASSERT_OK(err, "pthread_create")) {
+			skel->bss->release = 1;
+			goto out;
+		}
+		thread_created = true;
+	}
+
+	/* Wait until the worker thread triggers a flush. */
+	err = wait_for(&skel->bss->flush_entered);
+	if (!ASSERT_OK(err, "flush_entered"))
+		goto release;
+
+	flush_seen = true;
+
+	/* Force a reallocation during the flush. */
+	run_prog(skel->progs.try_realloc, "realloc_before_flush");
+	ASSERT_NULL(skel->bss->realloc_ptr, "realloc_before_flush");
+
+release:
+	skel->bss->release = 1;
+	if (thread_created) {
+		ASSERT_OK(pthread_join(thread, NULL), "pthread_join");
+		thread_created = false;
+		completed = ASSERT_OK(ctx.err, "free_run") &&
+			    ASSERT_OK(ctx.retval, "free_retval");
+	} else if (skel->bss->target_tid) {
+		err = wait_for(&skel->bss->worker_exited);
+		completed = ASSERT_OK(err, "worker_exited");
+	}
+	ASSERT_FALSE(skel->bss->timed_out, "flush_timed_out");
+
+	if (flush_seen && completed &&
+	    !run_prog(skel->progs.try_realloc, "realloc_after_flush")) {
+		ASSERT_EQ((unsigned long)skel->bss->realloc_ptr,
+			  (unsigned long)addr, "realloc_after_flush");
+		ASSERT_EQ(*addr, skel->rodata->new_marker, "new_marker");
+	}
+out:
+	arena_race__destroy(skel);
+}
+
+/*
+ * Force a race between a faulting thread in userspace and a
+ * free operation on the arena.
+ */
+static void test_fault_free_realloc(void)
+{
+	struct fault_thread_ctx fault = {};
+	struct arena_race *skel;
+	pthread_t fault_thread;
+	bool fault_created = false;
+	__u64 *addr;
+	__u64 expected, value;
+	int err, i;
+
+	skel = setup_arena(&addr, false);
+	if (!skel)
+		return;
+
+	skel->bss->realloc_after_free = 1;
+
+	fault.addr = addr;
+	err = pthread_create(&fault_thread, NULL, fault_reader_thread, &fault);
+	if (!ASSERT_OK(err, "pthread_create_fault"))
+		goto out;
+	fault_created = true;
+
+	for (i = 0; i < 1000; i++) {
+		LIBBPF_OPTS(bpf_test_run_opts, opts);
+
+		err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.free_page),
+					     &opts);
+		if (err) {
+			ASSERT_OK(err, "free_realloc");
+			break;
+		}
+		if (opts.retval) {
+			ASSERT_OK(opts.retval, "free_realloc");
+			break;
+		}
+		value = *addr;
+		expected = skel->bss->current_marker;
+		if (value != expected) {
+			ASSERT_EQ(value, expected, "marker_after_realloc");
+			break;
+		}
+	}
+
+	WRITE_ONCE(fault.stop, 1);
+	if (fault_created) {
+		ASSERT_OK(pthread_join(fault_thread, NULL), "pthread_join_fault");
+		fault_created = false;
+	}
+out:
+	WRITE_ONCE(fault.stop, 1);
+	if (fault_created)
+		pthread_join(fault_thread, NULL);
+	arena_race__destroy(skel);
+}
+
+void serial_test_arena_race(void)
+{
+	cpu_set_t cpuset;
+	int err;
+
+	err = sched_getaffinity(0, sizeof(cpuset), &cpuset);
+	if (!ASSERT_OK(err, "sched_getaffinity"))
+		return;
+	if (CPU_COUNT(&cpuset) < 2) {
+		printf("%s:SKIP: at least two runnable CPUs are required\n", __func__);
+		test__skip();
+		return;
+	}
+
+	if (test__start_subtest("free_before_flush"))
+		test_free_before_flush(false);
+	if (test__start_subtest("deferred_free_before_flush"))
+		test_free_before_flush(true);
+	if (test__start_subtest("fault_free_realloc"))
+		test_fault_free_realloc();
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_race.c b/tools/testing/selftests/bpf/progs/arena_race.c
new file mode 100644
index 0000000000000..56ebfd7273247
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_race.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_experimental.h"
+#include <bpf_arena_common.h>
+
+const volatile __u64 old_marker = 0x1111222233334444ULL;
+const volatile __u64 new_marker = 0x5555666677778888ULL;
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARENA);
+	__uint(map_flags, BPF_F_MMAPABLE);
+	__uint(max_entries, 4);
+#ifdef __TARGET_ARCH_arm64
+	__ulong(map_extra, 0x1ull << 32);
+#else
+	__ulong(map_extra, 0x1ull << 44);
+#endif
+} arena SEC(".maps");
+
+bool skip;
+
+void __arena *ptr;
+void __arena *realloc_ptr;
+bool realloc_after_free;
+__u64 current_marker;
+__u64 marker_seq;
+
+int target_tid;
+int pause_on_flush;
+int flush_entered;
+int release;
+int timed_out;
+
+__u64 trigger_pid_tgid;
+long trigger_syscall;
+int deferred_free;
+int worker_armed;
+int worker_exited;
+
+static __always_inline void wait_for_release(void)
+{
+	while (!*(volatile int *)&release && can_loop)
+		;
+	if (!*(volatile int *)&release)
+		timed_out = 1;
+}
+
+SEC("syscall")
+int alloc_old(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+	char __arena *base = arena_base(&arena);
+
+	realloc_ptr = NULL;
+	ptr = bpf_arena_alloc_pages(&arena, base + __PAGE_SIZE, 1,
+				    NUMA_NO_NODE, 0);
+	if (!ptr)
+		return 1;
+	p = (__u64 __arena *)ptr;
+	*p = old_marker;
+	current_marker = old_marker;
+#else
+	skip = true;
+#endif
+	return 0;
+}
+
+SEC("syscall")
+int free_page(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+	__u64 marker;
+
+	if (!ptr)
+		return 1;
+	bpf_arena_free_pages(&arena, ptr, 1);
+	if (!realloc_after_free)
+		return 0;
+
+	marker = new_marker + ++marker_seq;
+	realloc_ptr = bpf_arena_alloc_pages(&arena, ptr, 1, NUMA_NO_NODE, 0);
+	if (realloc_ptr)
+		ptr = realloc_ptr;
+	else
+		realloc_ptr = ptr;
+	p = (__u64 __arena *)ptr;
+	*p = marker;
+	current_marker = marker;
+#endif
+	return 0;
+}
+
+SEC("syscall")
+int try_realloc(void *ctx)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	__u64 __arena *p;
+
+	realloc_ptr = bpf_arena_alloc_pages(&arena, ptr, 1, NUMA_NO_NODE, 0);
+	if (realloc_ptr) {
+		ptr = realloc_ptr;
+		p = (__u64 __arena *)realloc_ptr;
+		*p = new_marker;
+		current_marker = new_marker;
+	}
+#endif
+	return 0;
+}
+
+SEC("tp_btf/sys_enter")
+int BPF_PROG(deferred_free_prog, struct pt_regs *regs, long id)
+{
+#if defined(__BPF_FEATURE_ADDR_SPACE_CAST) || defined(BPF_ARENA_FORCE_ASM)
+	if (!deferred_free || bpf_get_current_pid_tgid() != trigger_pid_tgid ||
+	    id != trigger_syscall)
+		return 0;
+
+	deferred_free = 0;
+	/* The worker can run on another CPU before the kfunc returns. */
+	worker_armed = 1;
+	bpf_arena_free_pages(&arena, ptr, 1);
+#endif
+	return 0;
+}
+
+SEC("fentry/arena_free_worker")
+int BPF_PROG(trace_free_worker, struct work_struct *work)
+{
+	if (worker_armed && !target_tid)
+		target_tid = (__u32)bpf_get_current_pid_tgid();
+	return 0;
+}
+
+SEC("fexit/arena_free_worker")
+int BPF_PROG(trace_free_worker_ret, struct work_struct *work)
+{
+	if ((__u32)bpf_get_current_pid_tgid() == target_tid)
+		worker_exited = 1;
+	return 0;
+}
+
+SEC("?fentry/flush_tlb_kernel_range")
+int BPF_PROG(trace_flush, unsigned long start, unsigned long end)
+{
+	if (!pause_on_flush ||
+	    (__u32)bpf_get_current_pid_tgid() != target_tid)
+		return 0;
+	flush_entered = 1;
+	wait_for_release();
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";

29/3 2026/09/25 11:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 15877
							output: 33
							thoughts: 1896
						

Thoughts:
**KMSAN Fuzzing Assessment**

Okay, let's break this down. My goal here is to determine whether this patch series warrants a dedicated KMSAN fuzzing session. I need to figure out if there's any potential for uninitialized memory exposure to userspace.

First, I need to understand what this patch is doing. It touches `arena.c`, `range_tree.c`, and related headers, along with some selftests. Based on the commit message, the core of the patch is fixing a race condition in BPF arena page freeing, specifically around the window between TLB flush, zap, and reuse/refault of pages. They are introducing states for availability of ranges in the range tree. The patch introduces a few new functions like `range_tree_set_avail`, `range_tree_set_unavail`, etc., and modifies existing functions like `arena_vm_fault`, `arena_map_mmap`, `arena_free_pages` and `arena_free_worker`.

My initial thought is that anything involving memory management, especially in a critical area like BPF, is a potential candidate for fuzzing. Race conditions and memory corruption are prime targets for KMSAN.

Now, let's dig into the code and potential issues.

*   `arena.c`: The core of the changes revolve around tracking the availability of memory ranges within the arena. New states (`ARENA_FREE_SPAN_...`) and associated functions (`range_tree_set_unavail`, etc.) indicate a focus on synchronizing access and ensuring that freed pages aren't reused prematurely. This is interesting: a new field `available` is added to `range_node`. I need to ensure the use of this new field doesn't introduce any new bugs.
*   `range_tree.c`: This is the data structure the patch is using to manage the memory ranges. I'll need to check how the availability flag is used in operations like setting, clearing, merging, and searching the range tree. I need to make sure that there isn't any unintentional exposure of potentially uninitialized memory here. I need to pay close attention to all memory allocation and initialization within `range_tree.c`, particularly around the `range_node` structure. Specifically I need to check:

    *   Are there any kernel structures being passed to user space (e.g., via `copy_to_user`, `put_user`, socket options, etc.)?
    *   Does the code contain any conditional logic or branching that relies on potentially uninitialized variables or struct fields?
*   `arena_vm_fault`, `arena_free_pages`, `arena_free_worker`, `range_tree.c`: I need to examine these functions specifically for potential issues, especially regarding how the `available` field is handled in conjunction with memory allocation, deallocation, and page fault handling. I'll also check any variables used in these functions to make sure they are initialized.

    *   I'll analyze `arena_vm_fault` very carefully, because of the spinlock handling, and calls to `vmf_insert_page`. I will examine `range_tree_set_unavail`.
    *   I will verify that all variables in `arena_free_pages` are initialized.
    *   I will examine the new `range_node` struct in `range_tree.c`, especially the initialization of the `available` field.
*   Let's check for pages mapped to userspace without being zeroed.

I'm checking all variables to be sure they are initialized. I need to track down how `struct range_node` is allocated, initialized, and how the `available` flag is used. This is critical. Specifically, let's examine `range_tree_set`:

I see that `left` is allocated with `kmalloc_nolock`, `left->available = false` and `if (new_rn) *new_rn = left` is always reached. I need to make sure there are no uninitialized pointers.

I see no direct use of `copy_to_user` or equivalent mechanisms.

OK, so after a thorough review, it seems that there's no obvious direct exposure of uninitialized memory to user space. The patch carefully manages the availability of memory ranges and initializes the new `available` field in `range_node` correctly. There doesn't appear to be any conditional logic relying on uninitialized variables.

Therefore, given the nature of the race condition being addressed (memory management and synchronization), I'm concluding this *does* warrant dedicated KMSAN fuzzing. While I didn't find any *obvious* bugs, the complexity and critical nature of this area warrant the extra scrutiny.

**NeedsKMSAN = true**
**KMSANReasoning


30/3 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "bpf_map_alloc_pages",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/arena.c=274=static struct bpf_map *arena_map_alloc(union bpf_attr *attr)\n--\nkernel/bpf/arena.c-322-\nkernel/bpf/arena.c:323:\terr = bpf_map_alloc_pages(\u0026arena-\u003emap, NUMA_NO_NODE, 1, \u0026arena-\u003escratch_page);\nkernel/bpf/arena.c-324-\tif (err)\n--\nkernel/bpf/arena.c=487=static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\n--\nkernel/bpf/arena.c-537-\t/* Account into memcg of the process that created bpf_arena */\nkernel/bpf/arena.c:538:\tret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, \u0026page);\nkernel/bpf/arena.c-539-\tif (ret) {\n--\nkernel/bpf/arena.c=718=static long arena_alloc_pages(struct bpf_arena *arena, long uaddr, long page_cnt, int node_id,\n--\nkernel/bpf/arena.c-785-\nkernel/bpf/arena.c:786:\t\tret = bpf_map_alloc_pages(\u0026arena-\u003emap, node_id, this_batch, pages);\nkernel/bpf/arena.c-787-\t\tif (ret)\n--\nkernel/bpf/syscall.c=605=static struct page *__bpf_alloc_page(int nid)\n--\nkernel/bpf/syscall.c-615-\nkernel/bpf/syscall.c:616:int bpf_map_alloc_pages(const struct bpf_map *map, int nid,\nkernel/bpf/syscall.c-617-\t\t\tunsigned long nr_pages, struct page **pages)\n"
}

31/3 2026/09/25 11:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 19319
							output: 36
							thoughts: 29
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing `__bpf_alloc_page` in `kernel/bpf/syscall.c`**

Alright, let's dive into this. My initial thought is to grab `__bpf_alloc_page` in `kernel/bpf/syscall.c`. I need to understand its behavior. Specifically, the intent is to see how this function is used; my assumption is that this is the memory allocator function for BPF programs, but I'll confirm. I'll use a `read-file` analysis approach, quickly examining the code around the function call to understand the context. I'm looking for clues about allocation size constraints, potential alignment requirements, error handling, and most critically, if there are any race conditions. I know BPF is security sensitive, and memory allocation is frequently the area where issues manifest. So this `read-file` is the starting point to understand the memory allocation policy.



32/3 2026/09/25 11:39 tool read-file
0m Args:
{
  "File": "kernel/bpf/syscall.c",
  "FirstLine": 600,
  "LineCount": 30
}

Results:
{
  "Contents": " 600:\t{\n 601:\t\treturn preempt_count() == 0 \u0026\u0026 !irqs_disabled() \u0026\u0026\n 602:\t\t\t!IS_ENABLED(CONFIG_PREEMPT_RT);\n 603:\t}\n 604:\t\n 605:\tstatic struct page *__bpf_alloc_page(int nid)\n 606:\t{\n 607:\t\tif (!can_alloc_pages())\n 608:\t\t\treturn alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);\n 609:\t\n 610:\t\treturn alloc_pages_node(nid,\n 611:\t\t\t\t\tGFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT\n 612:\t\t\t\t\t| __GFP_NOWARN,\n 613:\t\t\t\t\t0);\n 614:\t}\n 615:\t\n 616:\tint bpf_map_alloc_pages(const struct bpf_map *map, int nid,\n 617:\t\t\t\tunsigned long nr_pages, struct page **pages)\n 618:\t{\n 619:\t\tunsigned long i, j;\n 620:\t\tstruct page *pg;\n 621:\t\tint ret = 0;\n 622:\t\n 623:\t\tfor (i = 0; i \u003c nr_pages; i++) {\n 624:\t\t\tpg = __bpf_alloc_page(nid);\n 625:\t\n 626:\t\t\tif (pg) {\n 627:\t\t\t\tpages[i] = pg;\n 628:\t\t\t\tcontinue;\n 629:\t\t\t}\n"
}

33/3 2026/09/25 11:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20015
							output: 31
							thoughts: 45
						

Thoughts:
Here's my take on the thought process, as requested:

**Diving into `alloc_pages_nolock`**

Okay, so I'm deep in this code, and I just hit line 608: `alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);`. That function call, it immediately caught my attention. I know the general flow, but what's *specifically* going on with `alloc_pages_nolock` in *this* context? I mean, `alloc_pages` is fundamental, and `nolock` suggests a critical threading consideration, so it warrants a closer look. I need to refresh my memory on the exact semantics of this specific function. Let's see what that does. Time to dig in and remind myself of the details.



34/3 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "alloc_pages_nolock",
  "PathPrefix": "kernel/bpf/"
}

Results:
{
  "Output": "kernel/bpf/syscall.c=605=static struct page *__bpf_alloc_page(int nid)\n--\nkernel/bpf/syscall.c-607-\tif (!can_alloc_pages())\nkernel/bpf/syscall.c:608:\t\treturn alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);\nkernel/bpf/syscall.c-609-\n"
}

35/3 2026/09/25 11:39 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20374
							output: 20
							thoughts: 16
						

Thoughts:
Okay, let's dive into this code.

**Investigating `alloc_pages_nolock`**

Alright, my immediate thought is to trace the definition of `alloc_pages_nolock`. I need to understand its exact signature, how it's implemented, and what other functions call it. Understanding its context will greatly improve my ability to understand a code base. Knowing this function is defined will allow me to explore how the function will operate within a more complete context.



36/3 2026/09/25 11:39 tool grepper
0m Args:
{
  "Expression": "alloc_pages_nolock"
}

Results:
{
  "Output": "include/linux/gfp.h=25=static inline bool gfpflags_allow_spinning(const gfp_t gfp_flags)\n--\ninclude/linux/gfp.h-30-\t * All GFP_* flags including GFP_NOWAIT use one or both flags.\ninclude/linux/gfp.h:31:\t * alloc_pages_nolock() is the only API that doesn't specify either flag.\ninclude/linux/gfp.h-32-\t *\n--\ninclude/linux/gfp.h-36-\t * on any locks (i.e. only trylocks). There is no high level\ninclude/linux/gfp.h:37:\t * GFP_$FOO flag for this use in alloc_pages_nolock() as the\ninclude/linux/gfp.h-38-\t * regular page allocator doesn't fully support this\n--\ninclude/linux/gfp.h=309=static inline struct page *alloc_page_vma_noprof(gfp_t gfp,\n--\ninclude/linux/gfp.h-317-\ninclude/linux/gfp.h:318:struct page *alloc_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned int order);\ninclude/linux/gfp.h:319:#define alloc_pages_nolock(...)\t\t\talloc_hooks(alloc_pages_nolock_noprof(__VA_ARGS__))\ninclude/linux/gfp.h-320-\n--\nkernel/bpf/syscall.c=605=static struct page *__bpf_alloc_page(int nid)\n--\nkernel/bpf/syscall.c-607-\tif (!can_alloc_pages())\nkernel/bpf/syscall.c:608:\t\treturn alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);\nkernel/bpf/syscall.c-609-\n--\nmm/page_alloc.c=5343=static inline bool alloc_nolock_allowed(void)\n--\nmm/page_alloc.c-5355-/*\nmm/page_alloc.c:5356: * GFP flags to set for ALLOC_NOLOCK i.e. alloc_pages_nolock().\nmm/page_alloc.c-5357- *\n--\nmm/page_alloc.c-5363- *\nmm/page_alloc.c:5364: * Specify __GFP_NOWARN since failing alloc_pages_nolock() is not a reason\nmm/page_alloc.c-5365- * to warn. Also warn would trigger printk() which is unsafe from\n--\nmm/page_alloc.c-5373- * Though __GFP_NOMEMALLOC is not checked in the code path below,\nmm/page_alloc.c:5374: * specify it here to highlight that alloc_pages_nolock()\nmm/page_alloc.c-5375- * doesn't want to deplete reserves.\n--\nmm/page_alloc.c=5569=EXPORT_SYMBOL(__free_pages);\n--\nmm/page_alloc.c-5572- * Can be called while holding raw_spin_lock or from IRQ and NMI for any\nmm/page_alloc.c:5573: * page type (not only those that came from alloc_pages_nolock)\nmm/page_alloc.c-5574- */\n--\nmm/page_alloc.c=8041=struct page *alloc_frozen_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned int order)\n--\nmm/page_alloc.c-8048-/**\nmm/page_alloc.c:8049: * alloc_pages_nolock - opportunistic reentrant allocation from any context\nmm/page_alloc.c-8050- * @gfp_flags: GFP flags. Only __GFP_ACCOUNT, plus some flags that get set\n--\nmm/page_alloc.c-8056- * call from any context where RCU is watching (from atomic, NMI, and also\nmm/page_alloc.c:8057: * reentrant allocator -\u003e tracepoint -\u003e alloc_pages_nolock_noprof).\nmm/page_alloc.c-8058- * Allocation is best effort and to be expected to fail easily so nobody should\n--\nmm/page_alloc.c-8064- */\nmm/page_alloc.c:8065:struct page *alloc_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned int order)\nmm/page_alloc.c-8066-{\n--\nmm/page_alloc.c-8073-}\nmm/page_alloc.c:8074:EXPORT_SYMBOL_GPL(alloc_pages_nolock_noprof);\n--\nmm/page_owner.c=316=void __reset_page_owner(struct page *page, unsigned short order)\n--\nmm/page_owner.c-334-\t * to prevent issues in stack_depot_save().\nmm/page_owner.c:335:\t * This is similar to alloc_pages_nolock() gfp flags, but only used\nmm/page_owner.c-336-\t * to signal stack_depot to avoid spin_locks.\n"
}

37/3 2026/09/25 11:40 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21824
							output: 36
							thoughts: 20
						

Thoughts:
Here's a summary of my thought process, as requested:

**Diving into `bpf_map_alloc_pages`**

Okay, let's zero in on `bpf_map_alloc_pages`. I need to understand how this function operates. My immediate instinct is to look at how this impacts the page, as it is key to memory allocation, right? That’s where the real action is in BPF map management. I need to figure out what precisely is happening within this specific routine. I need to understand if there is a problem. What's the context? What structures are being manipulated? Are we talking about allocating physical pages, virtual pages, or something else entirely? The nuances of memory allocation in the kernel can be tricky, especially in the context of BPF. I need to make sure I understand the page lifecycle here. This will probably provide a breadcrumb to the problem, if there is one.



38/3 2026/09/25 11:40 tool read-file
0m Args:
{
  "File": "kernel/bpf/syscall.c",
  "FirstLine": 620,
  "LineCount": 40
}

Results:
{
  "Contents": " 620:\t\tstruct page *pg;\n 621:\t\tint ret = 0;\n 622:\t\n 623:\t\tfor (i = 0; i \u003c nr_pages; i++) {\n 624:\t\t\tpg = __bpf_alloc_page(nid);\n 625:\t\n 626:\t\t\tif (pg) {\n 627:\t\t\t\tpages[i] = pg;\n 628:\t\t\t\tcontinue;\n 629:\t\t\t}\n 630:\t\t\tfor (j = 0; j \u003c i; j++)\n 631:\t\t\t\tfree_pages_nolock(pages[j], 0);\n 632:\t\t\tret = -ENOMEM;\n 633:\t\t\tbreak;\n 634:\t\t}\n 635:\t\n 636:\t\treturn ret;\n 637:\t}\n 638:\t\n 639:\tstatic int btf_field_cmp(const void *a, const void *b)\n 640:\t{\n 641:\t\tconst struct btf_field *f1 = a, *f2 = b;\n 642:\t\n 643:\t\tif (f1-\u003eoffset \u003c f2-\u003eoffset)\n 644:\t\t\treturn -1;\n 645:\t\telse if (f1-\u003eoffset \u003e f2-\u003eoffset)\n 646:\t\t\treturn 1;\n 647:\t\treturn 0;\n 648:\t}\n 649:\t\n 650:\tstruct btf_field *btf_record_find(const struct btf_record *rec, u32 offset,\n 651:\t\t\t\t\t  u32 field_mask)\n 652:\t{\n 653:\t\tstruct btf_field *field;\n 654:\t\n 655:\t\tif (IS_ERR_OR_NULL(rec) || !(rec-\u003efield_mask \u0026 field_mask))\n 656:\t\t\treturn NULL;\n 657:\t\tfield = bsearch(\u0026offset, rec-\u003efields, rec-\u003ecnt, sizeof(rec-\u003efields[0]), btf_field_cmp);\n 658:\t\tif (!field || !(field-\u003etype \u0026 field_mask))\n 659:\t\t\treturn NULL;\n"
}

39/3 2026/09/25 11:40 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22656
							output: 36
							thoughts: 197
						

Thoughts:
**My Immediate Concerns Regarding `arena_vm_fault` and Memory Allocation**

Okay, so I'm looking at this `arena_vm_fault` function and my primary concern right now is the memory allocation happening within it. Specifically, I'm zeroing in on this sequence of calls: `bpf_map_alloc_pages`, `apply_to_page_range`, `flush_vmap_cache`, and then the `vmf_insert_page`. I see that memory is being allocated using `bpf_map_alloc_pages` and then initialized through `apply_to_page_range`, that's good practice. I hope `apply_to_page_range` calls `memset` to zero that memory. And `flush_vmap_cache` ensures that the initialization is visible, so it looks like it's okay.

But wait a minute, the code doesn't explicitly guarantee that the allocated pages are zeroed, and that's a potential vulnerability. There are security implications here, especially if this allocated memory is later exposed or reused. We need to be absolutely certain that there isn't any stale data leaking. It might be zeroed in this situation, but *is* it? We're dealing with sensitive kernel-level memory, and I'm not happy with the implicit assumption that the memory will be initialized, I need to know for sure. I am also concerned that if it's not being zeroed, there is another place that does this, or maybe it's not and we have a problem.

Let me go and check the implementation of `alloc_pages_nolock`. I need to review its behavior in a hurry, it's possible that the behavior is dependent on configuration.



40/3 2026/09/25 11:40 tool read-file
0m Args:
{
  "File": "mm/page_alloc.c",
  "FirstLine": 8060,
  "LineCount": 40
}

Results:
{
  "Contents": "8060:\t * See always fail conditions below.\n8061:\t *\n8062:\t * Return: allocated page or NULL on failure. NULL does not mean EBUSY or EAGAIN.\n8063:\t * It means ENOMEM. There is no reason to call it again and expect !NULL.\n8064:\t */\n8065:\tstruct page *alloc_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned int order)\n8066:\t{\n8067:\t\tstruct page *page;\n8068:\t\n8069:\t\tpage = alloc_frozen_pages_nolock_noprof(gfp_flags, nid, order);\n8070:\t\tif (page)\n8071:\t\t\tset_page_refcounted(page);\n8072:\t\treturn page;\n8073:\t}\n8074:\tEXPORT_SYMBOL_GPL(alloc_pages_nolock_noprof);\n"
}

41/3 2026/09/25 11:40 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23520
							output: 36
							thoughts: 18
						

Thoughts:
Okay, here's my interpretation of what's going on with `alloc_frozen_pages_nolock_noprof`, presented as my internal monologue, assuming I'm already pretty familiar with memory management concepts:

**Diving into `alloc_frozen_pages_nolock_noprof`**

Alright, let's break this down. The name alone tells me a lot: it's an allocation function specifically for "frozen" pages. That strongly suggests these pages are intended to be immutable or at least very rarely modified after they're allocated. The "nolock" part? Classic. We're talking about avoiding the overhead of explicit locking mechanisms. This likely means we're dealing with a specialized memory pool or some kind of pre-reserved region, probably under the assumption that the calls to this function are well-orchestrated and contention is minimized. The "noprof" suffix is particularly interesting. That implies this is a performance-critical path that they *really* don't want to profile, presumably because profiling would significantly impact the function's speed. Perhaps this function is called very frequently.

So, I'm picturing a highly optimized allocation path. Frozen pages likely serve a specific purpose, maybe for kernel structures that are initialized once and never change, or for read-only data regions. The lack of locking means they're prioritizing speed, assuming that the calling context handles any necessary synchronization at a higher level. The reluctance to profile further reinforces this dedication to performance. I'll need to dig deeper into the surrounding code to understand the specific use case and the strategies used to ensure safety without locks and profiling. The fact that the function name suggests a focus on speed means this function likely lies at the core of a critical component in the system.



42/3 2026/09/25 11:40 tool read-file
0m Args:
{
  "File": "mm/page_alloc.c",
  "FirstLine": 8035,
  "LineCount": 30
}

Results:
{
  "Contents": "8035:\t\tBUILD_BUG();\n8036:\t\treturn false;\n8037:\t}\n8038:\t\n8039:\t#endif /* CONFIG_UNACCEPTED_MEMORY */\n8040:\t\n8041:\tstruct page *alloc_frozen_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned int order)\n8042:\t{\n8043:\t\tif (nid == NUMA_NO_NODE)\n8044:\t\t\tnid = numa_node_id();\n8045:\t\n8046:\t\treturn __alloc_frozen_pages_noprof(gfp_flags, order, nid, NULL, ALLOC_NOLOCK);\n8047:\t}\n8048:\t/**\n8049:\t * alloc_pages_nolock - opportunistic reentrant allocation from any context\n8050:\t * @gfp_flags: GFP flags. Only __GFP_ACCOUNT, plus some flags that get set\n8051:\t *             internally regardless (see %gfp_nolock) are allowed.\n8052:\t * @nid: node to allocate from\n8053:\t * @order: allocation order size\n8054:\t *\n8055:\t * Allocates pages of a given order from the given node. This is safe to\n8056:\t * call from any context where RCU is watching (from atomic, NMI, and also\n8057:\t * reentrant allocator -\u003e tracepoint -\u003e alloc_pages_nolock_noprof).\n8058:\t * Allocation is best effort and to be expected to fail easily so nobody should\n8059:\t * rely on the success. Failures are not reported via warn_alloc().\n8060:\t * See always fail conditions below.\n8061:\t *\n8062:\t * Return: allocated page or NULL on failure. NULL does not mean EBUSY or EAGAIN.\n8063:\t * It means ENOMEM. There is no reason to call it again and expect !NULL.\n8064:\t */\n"
}

43/3 2026/09/25 11:40 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24474
							output: 33
							thoughts: 875
						

Thoughts:
**Analyzing the Arena VM Fault Handling**

Okay, let's break this down. My initial thought is to trace the execution path in `arena_vm_fault`. It starts with `bpf_map_alloc_pages`, and I'm asking myself, does `can_alloc_pages()` return true during a page fault? Yes, because we are in a sleepable context (preempt_count == 0, and interrupts are not disabled). So, we call `alloc_pages_node`, which is normal.

But then I see the patch, and I'm immediately interested in the change. They've replaced the direct assignment `vmf->page = page;` with `vmf_insert_page(vmf->vma, vmf->address, page);`. Why? Because `VM_MIXEDMAP` is now set in `arena_map_mmap`. This means the fault handler *must* use `vmf_insert_page` when `VM_MIXEDMAP` is enabled. It's the expected way.

Furthermore, it seems that there's an optimization going on. They no longer hold the arena spinlock during `vmf_insert_page`, because this avoids potential lock ordering issues or deadlocks with mmap lock. Instead, the range is marked as unavailable in the range tree while the spinlock is dropped, preventing concurrent allocations or frees in the same area. The range is then marked as available again upon re-acquiring the spinlock. It's a clever way to synchronize without holding a lock for the whole operation.

My next step is to examine the test case `arena_race.c`. It's clearly designed to reproduce a race condition. The tests, namely `free_before_flush`, `deferred_free_before_flush`, and `fault_free_realloc`, point to this: a use-after-free, a concurrent access, or a TLB flush issue. I'm guessing that this patch specifically targets this scenario.

Finally, I need more context. I'll check the commit message and dig into the git log of `kernel/bpf/arena.c` and/or `range_tree.c` to understand the root problem that this patch is trying to solve, and the specific race conditions it addresses. Understanding the history will reveal the motivation behind this patch and confirm my initial interpretation.



44/3 2026/09/25 11:40 tool grepper
0m Args:
{
  "Expression": "arena_vm_fault",
  "PathPrefix": "kernel/bpf/arena.c"
}

Results:
{
  "Output": "kernel/bpf/arena.c=472=static void arena_vm_close(struct vm_area_struct *vma)\n--\nkernel/bpf/arena.c-486-\nkernel/bpf/arena.c:487:static vm_fault_t arena_vm_fault(struct vm_fault *vmf)\nkernel/bpf/arena.c-488-{\n--\nkernel/bpf/arena.c=597=static const struct vm_operations_struct arena_vm_ops = {\n--\nkernel/bpf/arena.c-601-\t.close\t\t= arena_vm_close,\nkernel/bpf/arena.c:602:\t.fault          = arena_vm_fault,\nkernel/bpf/arena.c-603-};\n"
}

45/3 2026/09/25 11:40 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26092
							output: 302
							thoughts: 1784
						

Thoughts:
**KMSAN vs. KASAN: A Patch Assessment**

Okay, let's break this down. First, I need to understand whether this patch necessitates KMSAN or if standard KASAN and other tools are sufficient. The rule is clear: KMSAN is for *uninitialized* memory reads/leaks, not for bugs KASAN already catches. So, I need to carefully evaluate if the code touches on any of those KMSAN-specific scenarios.

I'll go through the checklist:

1.  **Kernel structures to userspace?** Nope. This patch is all internal, dealing with BPF arena management, specifically `range_node`, `arena_free_span`, and page fault handling. Nothing's being copied to userspace. Good.

2.  **Conditional logic with uninitialized vars?** Time to check every variable! In `arena_vm_fault`: everything gets initialized before use: `new_memcg`, `old_memcg`, `unavail_node`, `page`, `ret_fault`, `ret`. `flags` is just passed around as well and is checked elsewhere. Same for `arena_free_pages` and `arena_free_worker` and `range_tree.c`. It all looks good.

3.  **Complex data structures with potential uninitialized fields?** Let's see. The patch does touch on `struct arena_free_span` and `struct range_node`. However, in both cases, during their allocation with `kmalloc_nolock`, every field is explicitly assigned. No risk there.

4.  **Bounds checks exposing uninitialized memory?** Not in this patch. The patch targets a concurrency/race condition within a BPF arena concerning page reallocation and TLB flushes. The problems here would be more along the lines of locking deadlocks, use-after-free, page table corruption or range tree issues.

So, in short, I'm not seeing any scenarios where uninitialized memory is read or leaked. This looks like a classic race condition, which KASAN, LOCKDEP, and standard MM checks should detect if any bugs exist. No need for KMSAN.

Let's do a quick double-check for any subtle gotchas. Looks good in `arena_free_pages`: `unavail_node` is set before the `release_only` check, the pointer is valid.

Alright, I'm confident. I've considered all the aspects and assessed potential issues, and there are no signs of uninitialized memory reads or leaks. `NeedsKMSAN` should definitely be `false`. I'm ready to `set-results`.



46/3 2026/09/25 11:40 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch addresses race conditions and synchronization issues in the BPF arena subsystem during page faulting, reallocation, and freeing (ensuring ranges remain marked unavailable until TLB flush, PTE unmapping, and page freeing are complete).\n\nThe changes introduce state tracking in `struct arena_free_span` and an `available` flag in `struct range_node`, alongside managing transient unavailable ranges in the arena range tree (`arena-\u003ert`). \n\nFrom a memory sanitization perspective:\n1. No uninitialized memory is read or exposed: All newly added and modified heap allocations (`struct arena_free_span` and `struct range_node`) have all of their fields explicitly initialized immediately after allocation before any read access or insertion into data structures.\n2. No kernel data structures, buffers, or uninitialized padding are copied to userspace (no `copy_to_user`, `put_user`, socket options, etc.).\n3. The potential risks associated with these concurrency and synchronization modifications involve race conditions, use-after-free, double-free, and lock contention/deadlocks. These bug categories are detected by KASAN, LOCKDEP, and MM sanity checks in standard KASAN kernel builds.\n\nSince there are no uninitialized memory read risks or info-leaks, spawning a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch addresses race conditions and synchronization issues in the BPF arena subsystem during page faulting, reallocation, and freeing (ensuring ranges remain marked unavailable until TLB flush, PTE unmapping, and page freeing are complete).\n\nThe changes introduce state tracking in `struct arena_free_span` and an `available` flag in `struct range_node`, alongside managing transient unavailable ranges in the arena range tree (`arena-\u003ert`). \n\nFrom a memory sanitization perspective:\n1. No uninitialized memory is read or exposed: All newly added and modified heap allocations (`struct arena_free_span` and `struct range_node`) have all of their fields explicitly initialized immediately after allocation before any read access or insertion into data structures.\n2. No kernel data structures, buffers, or uninitialized padding are copied to userspace (no `copy_to_user`, `put_user`, socket options, etc.).\n3. The potential risks associated with these concurrency and synchronization modifications involve race conditions, use-after-free, double-free, and lock contention/deadlocks. These bug categories are detected by KASAN, LOCKDEP, and MM sanity checks in standard KASAN kernel builds.\n\nSince there are no uninitialized memory read risks or info-leaks, spawning a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)