summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorEmil Tsalapatis <emil@etsalapatis.com>2026-10-19 20:11:06 +0000
committerAlexei Starovoitov <ast@kernel.org>2026-10-20 00:18:31 +0000
commit05bee0638bacf4e9298bbf0bfef749b6a8ff4478 (patch)
treebb1e45b7f9ede2259471cb4a8d18d13e0bc1e9e6 /kernel
parent2ad9c9142d59f5748cce5da44ec46a14fdb66467 (diff)
bpf: Revalidate faulted arena pages before installing user mappings
A user fault on an arena pins the page that the kernel arena maps, drops the arena spinlock and returns the page to the core fault code, which then locks the folio and installs the user PTE. A concurrent free can clear the kernel mapping in that window. The page reference prevents a use-after-free, but the fault can still install a user PTE for a page the arena has already released. User space and BPF programs then see different memory at the same arena offset. Close the window from the fault side. Lock the page, recheck that the kernel mapping still points to it, and return it with VM_FAULT_LOCKED so that it stays locked until the core fault code has installed the user PTE. If a free removed the page, drop it and return VM_FAULT_NOPAGE so that the fault starts over. Return VM_FAULT_HWPOISON for a poisoned page. This change alone does not close the race, because the free path does not yet wait for the page lock. The following change makes frees wait for in-flight faults before zapping user mappings. Fixes: 317460317a02 ("bpf: Introduce bpf_arena.") Suggested-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20261019201108.4552-2-emil@etsalapatis.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/arena.c18
1 files changed, 16 insertions, 2 deletions
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 0ff707707da0..5e0e0837980b 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -584,8 +584,22 @@ out:
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
if (new_page)
free_pages_nolock(new_page, 0);
- vmf->page = page;
- return 0;
+
+ /*
+ * A free clears the kernel pte and then takes the page lock before it
+ * zaps user vma-s. Either it waits for this fault and zaps its pte, or
+ * the page is gone from here.
+ */
+ lock_page(page);
+ if (vmalloc_to_page((void *)kaddr) == page && !PageHWPoison(page)) {
+ vmf->page = page;
+ return VM_FAULT_LOCKED;
+ }
+ /* The page was freed meanwhile. Let the fault start over. */
+ fault_ret = PageHWPoison(page) ? VM_FAULT_HWPOISON : VM_FAULT_NOPAGE;
+ unlock_page(page);
+ put_page(page);
+ return fault_ret;
out_err_locked_memcg:
bpf_map_memcg_exit(old_memcg, new_memcg);