Merge branch 'arena_direct_access'

Tejun Heo says:

====================
This makes BPF arena memory directly dereferenceable from kernel code
(struct_ops callbacks, kfuncs). Each arena gets a per-arena scratch page
that an arch fault hook installs into empty PTEs on kernel-side faults,
after KFENCE. The faulting instruction retries and the violation is reported
through the program's BPF stream.

v4:
- Patch 1: note that the strict-zero cmpxchg is narrower than pte_none() in
  inline comments on both x86 and arm64. (Andrea)
- Patch 2: stub bpf_arena_handle_page_fault() for !CONFIG_BPF_SYSCALL via a
  new include/linux/bpf_defs.h. (lkp)
- Patch 7: scx_arena_alloc() retries via a loop instead of a single retry on
  pool growth. (Andrea)
- Picked up Reviewed-by tags from Emil and Andrea.

v3: https://lore.kernel.org/r/20260520235052.4180316-1-tj@kernel.org
v2: https://lore.kernel.org/r/20260517211232.1670594-1-tj@kernel.org
v1 (RFC): https://lore.kernel.org/r/20260427105109.2554518-1-tj@kernel.org

Motivation
----------

sched_ext's ops_cid.set_cmask() hands the BPF scheduler a struct scx_cmask
*. The kernel translates a kernel cpumask to a cmask, but it had no way to
write into the arena, so the cmask lived in kernel memory and was passed as
a trusted pointer. BPF cmask helpers all operate on arena cmasks though, so
the BPF side had to word-by-word probe-read the kernel cmask into an arena
cmask via cmask_copy_from_kernel() before any helper could touch it. It
works, but is clumsy.

The shape isn't unique to set_cmask. Sub-scheduler support is on the way and
more sched_ext callbacks will want to pass structured data to BPF. Anywhere
a kfunc or struct_ops callback wants to hand a struct to a BPF program,
arena residence is the natural answer.

Approach
--------

Each arena gets a per-arena scratch page. Arenas stay sparsely mapped as
today - PTEs are populated only for allocated pages. A new arch fault hook
(bpf_arena_handle_page_fault) is wired into x86 page_fault_oops() and arm64
__do_kernel_fault(), after KFENCE. When a kernel-side access faults inside
an arena's kern_vm range, the helper walks the stack to find the BPF program
responsible, range-checks the fault address against prog->aux->arena, and
atomically installs the scratch page into the empty PTE via the new
ptep_try_set() wrapper. The kernel instruction retries and reads/writes the
scratch page. Free paths and map destruction treat scratch as non-owned.
Real allocation refuses to overwrite scratch (apply_range_set_cb returns
-EBUSY). A scratched address stays dead until map destroy, since its
presence means the BPF program has already malfunctioned.

The mechanism is default behavior - no UAPI flag.

What this preserves
-------------------

All the debugging properties of today's sparse-PTE design are preserved:

* BPF programs still fault on unmapped arena accesses. The fault semantics
  (instruction retry with rdst = 0) and the violation report through
  bpf_streams are unchanged for prog-side accesses.

* The first kernel-side touch of an unmapped address is reported via
  bpf_streams the same way as a prog-side fault, with the stack walk
  attributing it to the originating prog.

* User-side fault on a never-scratched address still lazy-allocates a real
  page (or returns SIGSEGV under BPF_F_SEGV_ON_FAULT). User-side fault on a
  scratched address SIGSEGVs.

What changes for the kernel-side caller is just that an unmapped deref no
longer oopses - it retries through the scratch page and emits a violation
report. The same shape today's BPF instruction faults have.

Patches 1-2 (atomic PTE install + arena scratch-page recovery)
--------------------------------------------------------------

  mm: Add ptep_try_set() for lockless empty-slot installs
  bpf: Recover arena kernel faults with scratch page

Patches 3-5 (helpers used by struct_ops registration)
-----------------------------------------------------

  bpf: Add sleepable variant of bpf_arena_alloc_pages for kernel callers
  bpf: Add bpf_struct_ops_for_each_prog()
  bpf/arena: Add bpf_arena_map_kern_vm_start() and bpf_prog_arena()
====================

Link: https://lore.kernel.org/bpf/20260522172219.1423324-1-tj@kernel.org/
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-05-25 08:35:07 -07:00
commit 8496d9020f
11 changed files with 328 additions and 47 deletions

View File

@ -480,6 +480,20 @@ In order to accommodate such requirements, the verifier will enforce strict
PTR_TO_BTF_ID type matching if two types have the exact same name, with one
being suffixed with ``___init``.
2.8 Accessing arena memory through kfunc arguments
--------------------------------------------------
A read or write at any address inside an arena does not oops the kernel.
Unallocated arena pages are lazily backed by a scratch page and the
access is reported through the program's BPF stream as an error. Only
the BPF program's correctness is affected; the kernel itself remains
intact.
The arena is followed by a ``GUARD_SZ / 2`` (32 KiB) guard region that
is also covered by this recovery. A kfunc handed an arena pointer may
therefore access up to ``GUARD_SZ / 2`` past it without bounds-checking
against the arena. Larger accesses must verify the range explicitly.
.. _BPF_kfunc_lifecycle_expectations:
3. kfunc lifecycle expectations

View File

@ -1830,6 +1830,18 @@ static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
return __ptep_get_and_clear(mm, addr, ptep);
}
/*
* Note: strictly-zero compare is narrower than pte_none(), but the gap is
* harmless: a fresh kernel PTE has no software bits set.
*/
static inline bool ptep_try_set(pte_t *ptep, pte_t new_pte)
{
pteval_t old = 0;
return try_cmpxchg(&pte_val(*ptep), &old, pte_val(new_pte));
}
#define ptep_try_set ptep_try_set
#define test_and_clear_young_ptes test_and_clear_young_ptes
static inline bool test_and_clear_young_ptes(struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, unsigned int nr)

View File

@ -9,6 +9,7 @@
#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/bpf_defs.h>
#include <linux/extable.h>
#include <linux/kfence.h>
#include <linux/signal.h>
@ -436,9 +437,12 @@ static void __do_kernel_fault(unsigned long addr, unsigned long esr,
} else if (is_pkvm_stage2_abort(esr)) {
msg = "access to hypervisor-protected memory";
} else {
if (esr_fsc_is_translation_fault(esr) &&
kfence_handle_page_fault(addr, esr & ESR_ELx_WNR, regs))
return;
if (esr_fsc_is_translation_fault(esr)) {
if (kfence_handle_page_fault(addr, esr & ESR_ELx_WNR, regs))
return;
if (bpf_arena_handle_page_fault(addr, esr & ESR_ELx_WNR, regs->pc))
return;
}
msg = "paging request";
}

View File

@ -1284,6 +1284,18 @@ static inline void ptep_set_wrprotect(struct mm_struct *mm,
} while (!try_cmpxchg((long *)&ptep->pte, (long *)&old_pte, *(long *)&new_pte));
}
/*
* Note: strictly-zero compare is narrower than pte_none(), but the gap is
* harmless: _PAGE_DIRTY and _PAGE_ACCESSED aren't set on untouched kernel PTEs.
*/
static inline bool ptep_try_set(pte_t *ptep, pte_t new_pte)
{
pte_t old_pte = __pte(0);
return try_cmpxchg((long *)&ptep->pte, (long *)&old_pte, *(long *)&new_pte);
}
#define ptep_try_set ptep_try_set
#define flush_tlb_fix_spurious_fault(vma, address, ptep) do { } while (0)
#define __HAVE_ARCH_PMDP_SET_ACCESS_FLAGS

View File

@ -8,6 +8,7 @@
#include <linux/sched/task_stack.h> /* task_stack_*(), ... */
#include <linux/kdebug.h> /* oops_begin/end, ... */
#include <linux/memblock.h> /* max_low_pfn */
#include <linux/bpf_defs.h> /* bpf_arena_handle_page_fault */
#include <linux/kfence.h> /* kfence_handle_page_fault */
#include <linux/kprobes.h> /* NOKPROBE_SYMBOL, ... */
#include <linux/mmiotrace.h> /* kmmio_handler, ... */
@ -688,10 +689,13 @@ page_fault_oops(struct pt_regs *regs, unsigned long error_code,
if (IS_ENABLED(CONFIG_EFI))
efi_crash_gracefully_on_page_fault(address, regs);
/* Only not-present faults should be handled by KFENCE. */
if (!(error_code & X86_PF_PROT) &&
kfence_handle_page_fault(address, error_code & X86_PF_WRITE, regs))
return;
/* Only not-present faults should be handled by KFENCE or BPF arena. */
if (!(error_code & X86_PF_PROT)) {
if (kfence_handle_page_fault(address, error_code & X86_PF_WRITE, regs))
return;
if (bpf_arena_handle_page_fault(address, error_code & X86_PF_WRITE, regs->ip))
return;
}
oops:
/*

View File

@ -6,6 +6,7 @@
#include <uapi/linux/bpf.h>
#include <uapi/linux/filter.h>
#include <linux/bpf_defs.h>
#include <crypto/sha2.h>
#include <linux/workqueue.h>
@ -617,6 +618,8 @@ void bpf_rb_root_free(const struct btf_field *field, void *rb_root,
struct bpf_spin_lock *spin_lock);
u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena);
u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena);
u64 bpf_arena_map_kern_vm_start(struct bpf_map *map);
struct bpf_map *bpf_prog_arena(struct bpf_prog *prog);
int bpf_obj_name_cpy(char *dst, const char *src, unsigned int size);
struct bpf_offload_dev;
@ -678,6 +681,8 @@ int bpf_dynptr_from_file_sleepable(struct file *file, u32 flags,
void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt, int node_id,
u64 flags);
void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt);
void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt, int node_id,
u64 flags);
#else
static inline void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
int node_id, u64 flags)
@ -688,6 +693,12 @@ static inline void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr
static inline void bpf_arena_free_pages_non_sleepable(void *p__map, void *ptr__ign, u32 page_cnt)
{
}
static inline void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
int node_id, u64 flags)
{
return NULL;
}
#endif
extern const struct bpf_map_ops bpf_map_offload_ops;
@ -2129,6 +2140,9 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map);
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog);
void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux);
u32 bpf_struct_ops_id(const void *kdata);
int bpf_struct_ops_for_each_prog(const void *kdata,
int (*cb)(struct bpf_prog *prog, void *data),
void *data);
#ifdef CONFIG_NET
/* Define it here to avoid the use of forward declaration */

19
include/linux/bpf_defs.h Normal file
View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Subset of bpf.h declarations, split out so files that need only these
* declarations can avoid bpf.h's full include cost.
*/
#ifndef _LINUX_BPF_DEFS_H
#define _LINUX_BPF_DEFS_H
#ifdef CONFIG_BPF_SYSCALL
bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write, unsigned long fault_ip);
#else
static inline bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,
unsigned long fault_ip)
{
return false;
}
#endif
#endif /* _LINUX_BPF_DEFS_H */

View File

@ -1036,6 +1036,31 @@ static inline void ptep_set_wrprotect(struct mm_struct *mm, unsigned long addres
}
#endif
#ifndef ptep_try_set
/**
* ptep_try_set - atomically set an empty kernel PTE
* @ptep: page table entry
* @new_pte: value to install
*
* Atomically set *@ptep to @new_pte iff *@ptep is pte_none(). Return true on
* success, false if the slot was already populated or the arch has no
* implementation.
*
* For special kernel page tables only - never user page tables. The caller must
* prevent concurrent teardown of @ptep and must accept that other writers may
* race. Concurrent clearers must use ptep_get_and_clear() so racing accesses
* agree on the outcome.
*
* Architectures opt in by providing a cmpxchg-based override and defining
* ptep_try_set as an identity macro. The generic stub returns false, which is
* correct for callers that fall through to oops on failure.
*/
static inline bool ptep_try_set(pte_t *ptep, pte_t new_pte)
{
return false;
}
#endif
#ifndef wrprotect_ptes
/**
* wrprotect_ptes - Write-protect PTEs that map consecutive pages of the same

View File

@ -53,6 +53,7 @@ struct bpf_arena {
u64 user_vm_start;
u64 user_vm_end;
struct vm_struct *kern_vm;
struct page *scratch_page;
struct range_tree rt;
/* protects rt */
rqspinlock_t spinlock;
@ -83,6 +84,32 @@ u64 bpf_arena_get_user_vm_start(struct bpf_arena *arena)
return arena ? arena->user_vm_start : 0;
}
/**
* bpf_arena_map_kern_vm_start - kern_vm_start lookup by struct bpf_map *
* @map: a BPF_MAP_TYPE_ARENA map
*
* Return @map's kern_vm_start.
*/
u64 bpf_arena_map_kern_vm_start(struct bpf_map *map)
{
return bpf_arena_get_kern_vm_start(container_of(map, struct bpf_arena, map));
}
/**
* bpf_prog_arena - return the bpf_map of the arena referenced by @prog
* @prog: a loaded BPF program
*
* The verifier enforces at most one arena per program and stores it in
* prog->aux->arena. Return that arena's underlying bpf_map, or NULL if
* @prog does not reference an arena.
*/
struct bpf_map *bpf_prog_arena(struct bpf_prog *prog)
{
struct bpf_arena *arena = prog->aux->arena;
return arena ? &arena->map : NULL;
}
static long arena_map_peek_elem(struct bpf_map *map, void *value)
{
return -EOPNOTSUPP;
@ -118,6 +145,11 @@ struct apply_range_data {
int i;
};
struct clear_range_data {
struct llist_head *free_pages;
struct page *scratch_page;
};
static int apply_range_set_cb(pte_t *pte, unsigned long addr, void *data)
{
struct apply_range_data *d = data;
@ -144,33 +176,59 @@ static void flush_vmap_cache(unsigned long start, unsigned long size)
flush_cache_vmap(start, start + size);
}
static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *free_pages)
static int apply_range_clear_cb(pte_t *pte, unsigned long addr, void *data)
{
struct clear_range_data *d = data;
pte_t old_pte;
struct page *page;
/* sanity check */
old_pte = ptep_get(pte);
/*
* Pairs with ptep_try_set() in the kernel-fault scratch installer.
* Both sides must be atomic.
*/
old_pte = ptep_get_and_clear(&init_mm, addr, pte);
if (pte_none(old_pte) || !pte_present(old_pte))
return 0; /* nothing to do */
return 0;
page = pte_page(old_pte);
if (WARN_ON_ONCE(!page))
return -EINVAL;
pte_clear(&init_mm, addr, pte);
/*
* Skip the per-arena scratch page. A kernel fault on an unallocated uaddr
* scratches its PTE. A later bpf_arena_free_pages() over that range walks
* here. Without the skip, scratch_page would be freed.
*/
if (page == d->scratch_page)
return 0;
/* Add page to the list so it is freed later */
if (free_pages)
__llist_add(&page->pcp_llist, free_pages);
__llist_add(&page->pcp_llist, d->free_pages);
return 0;
}
static int apply_range_set_scratch_cb(pte_t *pte, unsigned long addr, void *data)
{
struct page *scratch_page = data;
if (!pte_none(ptep_get(pte)))
return 0;
/*
* Best-effort install. ptep_try_set() returns false only if another
* installer (real allocation or concurrent fault) won the cmpxchg.
* Their PTE is already valid, so the access retry succeeds.
*
* No flush_tlb_kernel_range() needed. Stale "not mapped" entries just
* cause one extra re-fault through this same path.
*/
ptep_try_set(pte, mk_pte(scratch_page, PAGE_KERNEL));
return 0;
}
static int populate_pgtable_except_pte(struct bpf_arena *arena)
{
/* Populate intermediates for the recovery range (4 GiB + upper half-guard). */
return apply_to_page_range(&init_mm, bpf_arena_get_kern_vm_start(arena),
KERN_VM_SZ - GUARD_SZ, apply_range_set_cb, NULL);
SZ_4G + GUARD_SZ / 2, apply_range_set_cb, NULL);
}
static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
@ -221,22 +279,29 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
init_irq_work(&arena->free_irq, arena_free_irq);
INIT_WORK(&arena->free_work, arena_free_worker);
bpf_map_init_from_attr(&arena->map, attr);
err = bpf_map_alloc_pages(&arena->map, NUMA_NO_NODE, 1, &arena->scratch_page);
if (err)
goto err_free_arena;
range_tree_init(&arena->rt);
err = range_tree_set(&arena->rt, 0, attr->max_entries);
if (err) {
bpf_map_area_free(arena);
goto err;
}
if (err)
goto err_free_scratch;
mutex_init(&arena->lock);
raw_res_spin_lock_init(&arena->spinlock);
err = populate_pgtable_except_pte(arena);
if (err) {
range_tree_destroy(&arena->rt);
bpf_map_area_free(arena);
goto err;
}
if (err)
goto err_destroy_rt;
return &arena->map;
err_destroy_rt:
range_tree_destroy(&arena->rt);
err_free_scratch:
__free_page(arena->scratch_page);
err_free_arena:
bpf_map_area_free(arena);
err:
free_vm_area(kern_vm);
return ERR_PTR(err);
@ -244,6 +309,7 @@ static struct bpf_map *arena_map_alloc(union bpf_attr *attr)
static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
{
struct bpf_arena *arena = data;
struct page *page;
pte_t pte;
@ -251,6 +317,12 @@ static int existing_page_cb(pte_t *ptep, unsigned long addr, void *data)
if (!pte_present(pte)) /* sanity check */
return 0;
page = pte_page(pte);
/*
* Skip the scratch page. The walk is page-table-driven, not range-tree-driven,
* so it can visit scratch PTEs at uaddrs the BPF program never allocated.
*/
if (page == arena->scratch_page)
return 0;
/*
* We do not update pte here:
* 1. Nobody should be accessing bpf_arena's range outside of a kernel bug
@ -286,9 +358,10 @@ static void arena_map_free(struct bpf_map *map)
* free those pages.
*/
apply_to_existing_page_range(&init_mm, bpf_arena_get_kern_vm_start(arena),
KERN_VM_SZ - GUARD_SZ, existing_page_cb, NULL);
SZ_4G + GUARD_SZ / 2, existing_page_cb, arena);
free_vm_area(arena->kern_vm);
range_tree_destroy(&arena->rt);
__free_page(arena->scratch_page);
bpf_map_area_free(arena);
}
@ -384,33 +457,37 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
return VM_FAULT_RETRY;
page = vmalloc_to_page((void *)kaddr);
if (page)
if (page) {
if (page == arena->scratch_page)
/* BPF triggered scratch here; don't lazy-alloc over it */
goto out_sigsegv;
/* already have a page vmap-ed */
goto out;
}
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
/* User space requested to segfault when page is not allocated by bpf prog */
goto out_unlock_sigsegv;
goto out_sigsegv_memcg;
ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
if (ret)
goto out_unlock_sigsegv;
goto out_sigsegv_memcg;
struct apply_range_data data = { .pages = &page, .i = 0 };
/* Account into memcg of the process that created bpf_arena */
ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
range_tree_set(&arena->rt, vmf->pgoff, 1);
goto out_unlock_sigsegv;
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);
free_pages_nolock(page, 0);
goto out_unlock_sigsegv;
goto out_sigsegv_memcg;
}
flush_vmap_cache(kaddr, PAGE_SIZE);
bpf_map_memcg_exit(old_memcg, new_memcg);
@ -419,8 +496,9 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
vmf->page = page;
return 0;
out_unlock_sigsegv:
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;
}
@ -685,6 +763,7 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
struct llist_head free_pages;
struct llist_node *pos, *t;
struct arena_free_span *s;
struct clear_range_data cdata;
unsigned long flags;
int ret = 0;
@ -713,9 +792,11 @@ static void arena_free_pages(struct bpf_arena *arena, long uaddr, long page_cnt,
range_tree_set(&arena->rt, pgoff, page_cnt);
init_llist_head(&free_pages);
cdata.free_pages = &free_pages;
cdata.scratch_page = arena->scratch_page;
/* clear ptes and collect struct pages */
apply_to_existing_page_range(&init_mm, kaddr, page_cnt << PAGE_SHIFT,
apply_range_clear_cb, &free_pages);
apply_range_clear_cb, &cdata);
/* drop the lock to do the tlb flush and zap pages */
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
@ -805,6 +886,7 @@ static void arena_free_worker(struct work_struct *work)
struct arena_free_span *s;
u64 arena_vm_start, user_vm_start;
struct llist_head free_pages;
struct clear_range_data cdata;
struct page *page;
unsigned long full_uaddr;
long kaddr, page_cnt, pgoff;
@ -818,6 +900,8 @@ static void arena_free_worker(struct work_struct *work)
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
init_llist_head(&free_pages);
cdata.free_pages = &free_pages;
cdata.scratch_page = arena->scratch_page;
arena_vm_start = bpf_arena_get_kern_vm_start(arena);
user_vm_start = bpf_arena_get_user_vm_start(arena);
@ -830,7 +914,7 @@ static void arena_free_worker(struct work_struct *work)
/* 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, &free_pages);
apply_range_clear_cb, &cdata);
range_tree_set(&arena->rt, pgoff, page_cnt);
}
@ -893,6 +977,19 @@ void *bpf_arena_alloc_pages_non_sleepable(void *p__map, void *addr__ign, u32 pag
return (void *)arena_alloc_pages(arena, (long)addr__ign, page_cnt, node_id, false);
}
void *bpf_arena_alloc_pages_sleepable(void *p__map, void *addr__ign, u32 page_cnt,
int node_id, u64 flags)
{
struct bpf_map *map = p__map;
struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
if (map->map_type != BPF_MAP_TYPE_ARENA || flags || !page_cnt)
return NULL;
return (void *)arena_alloc_pages(arena, (long)addr__ign, page_cnt, node_id, true);
}
__bpf_kfunc void bpf_arena_free_pages(void *p__map, void *ptr__ign, u32 page_cnt)
{
struct bpf_map *map = p__map;
@ -945,23 +1042,12 @@ static int __init kfunc_init(void)
}
late_initcall(kfunc_init);
void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned long fault_ip)
static void __bpf_prog_report_arena_violation(struct bpf_prog *prog, bool write,
unsigned long addr, unsigned long fault_ip)
{
struct bpf_stream_stage ss;
struct bpf_prog *prog;
u64 user_vm_start;
/*
* The RCU read lock is held to safely traverse the latch tree, but we
* don't need its protection when accessing the prog, since it will not
* disappear while we are handling the fault.
*/
rcu_read_lock();
prog = bpf_prog_ksym_find(fault_ip);
rcu_read_unlock();
if (!prog)
return;
/* Use main prog for stream access */
prog = prog->aux->main_prog_aux->prog;
@ -974,3 +1060,53 @@ void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned lo
bpf_stream_dump_stack(ss);
}));
}
bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write, unsigned long fault_ip)
{
struct bpf_arena *arena;
struct bpf_prog *prog;
unsigned long kbase;
unsigned long page_addr = addr & PAGE_MASK;
prog = bpf_prog_find_from_stack();
if (!prog)
return false;
arena = prog->aux->arena;
/* a prog not using arena may be on stack, so arena can be NULL */
if (!arena)
return false;
kbase = bpf_arena_get_kern_vm_start(arena);
/*
* Recovery covers the 4 GiB mappable band plus the upper half-guard.
* Lower guard is unreachable from kfuncs; an address there indicates
* a different bug class - leave it to the regular kernel oops path.
*/
if (page_addr < kbase || page_addr >= kbase + SZ_4G + GUARD_SZ / 2)
return false;
apply_to_page_range(&init_mm, page_addr, PAGE_SIZE,
apply_range_set_scratch_cb, arena->scratch_page);
flush_vmap_cache(page_addr, PAGE_SIZE);
__bpf_prog_report_arena_violation(prog, is_write, page_addr - kbase, fault_ip);
return true;
}
void bpf_prog_report_arena_violation(bool write, unsigned long addr, unsigned long fault_ip)
{
struct bpf_prog *prog;
/*
* The RCU read lock is held to safely traverse the latch tree, but we
* don't need its protection when accessing the prog, since it will not
* disappear while we are handling the fault.
*/
rcu_read_lock();
prog = bpf_prog_ksym_find(fault_ip);
rcu_read_unlock();
if (!prog)
return;
__bpf_prog_report_arena_violation(prog, write, addr, fault_ip);
}

View File

@ -1204,6 +1204,42 @@ u32 bpf_struct_ops_id(const void *kdata)
}
EXPORT_SYMBOL_GPL(bpf_struct_ops_id);
/**
* bpf_struct_ops_for_each_prog - Invoke @cb for each member prog
* @kdata: kernel-side struct_ops vmtable (the @kdata arg to ->reg/->update/->unreg)
* @cb: callback invoked once per member prog; non-zero return stops iteration
* @data: opaque argument passed to @cb
*
* Walks the struct_ops member progs registered on the map containing @kdata.
* Intended for use from struct_ops ->reg() callbacks (and similar) that need to
* inspect the loaded BPF programs (for example to discover maps they reference
* via @prog->aux->used_maps).
*
* Return 0 if iteration completed, otherwise the first non-zero @cb return.
*/
int bpf_struct_ops_for_each_prog(const void *kdata,
int (*cb)(struct bpf_prog *prog, void *data),
void *data)
{
struct bpf_struct_ops_value *kvalue;
struct bpf_struct_ops_map *st_map;
u32 i;
int ret;
kvalue = container_of(kdata, struct bpf_struct_ops_value, data);
st_map = container_of(kvalue, struct bpf_struct_ops_map, kvalue);
for (i = 0; i < st_map->funcs_cnt; i++) {
if (!st_map->links[i])
continue;
ret = cb(st_map->links[i]->prog, data);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(bpf_struct_ops_for_each_prog);
static bool bpf_struct_ops_valid_to_reg(struct bpf_map *map)
{
struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map;

View File

@ -3376,6 +3376,11 @@ __weak u64 bpf_arena_get_kern_vm_start(struct bpf_arena *arena)
{
return 0;
}
__weak bool bpf_arena_handle_page_fault(unsigned long addr, bool is_write,
unsigned long fault_ip)
{
return false;
}
#ifdef CONFIG_BPF_SYSCALL
static int __init bpf_global_ma_init(void)