* [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory
2026-08-25 9:49 [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Khawar Ahemad
@ 2026-08-25 9:49 ` Khawar Ahemad
2026-08-25 10:11 ` sashiko-bot
` (2 more replies)
2026-08-25 9:49 ` [PATCH bpf-next v6 2/4] bpf: arena: allocate the fault-in page outside the lock Khawar Ahemad
` (3 subsequent siblings)
4 siblings, 3 replies; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 9:49 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123
From: Jiayuan Chen <jiayuan.chen@linux.dev>
bpf_map_alloc_pages() picks the allocator via can_alloc_pages(), a
conservative guess for BPF program context that is always false under
PREEMPT_RT. So even a caller that really is sleepable gets the
non-blocking allocator, which never reclaims and never engages the OOM
machinery.
Add bpf_map_alloc_page_sleepable() for callers that know they are
sleepable. Like the other bpf map allocators it places the page on the
map's numa_node and does not follow the faulting task's NUMA mempolicy;
arena memory is shared, so the map's node is the right placement policy.
The next patch uses it from the arena page fault handler.
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Khawar Ahemad <ahemadkhawar123@gmail.com>
---
include/linux/bpf.h | 1 +
kernel/bpf/syscall.c | 21 +++++++++++++++++----
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b3cd28d9e3..c817c99d29 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
unsigned long nr_pages, struct page **page_array);
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);
#ifdef CONFIG_MEMCG
void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
struct mem_cgroup **new_memcg);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6874ba1424..f9b81638e5 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -602,15 +602,14 @@ static bool can_alloc_pages(void)
!IS_ENABLED(CONFIG_PREEMPT_RT);
}
+#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)
+
static struct page *__bpf_alloc_page(int nid)
{
if (!can_alloc_pages())
return alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);
- return alloc_pages_node(nid,
- GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT
- | __GFP_NOWARN,
- 0);
+ return alloc_pages_node(nid, BPF_PAGE_GFP, 0);
}
int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
@@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
return ret;
}
+/*
+ * For callers that know they run in a sleepable context, e.g. a user page
+ * fault handler. can_alloc_pages() is a conservative guess made for BPF
+ * program context - notably it is always false on PREEMPT_RT - so going
+ * through bpf_map_alloc_pages() there would needlessly pick the
+ * non-blocking allocator, which never reclaims and never engages the OOM
+ * machinery.
+ */
+struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)
+{
+ might_sleep();
+ return alloc_pages_node(map->numa_node, BPF_PAGE_GFP, 0);
+}
+
static int btf_field_cmp(const void *a, const void *b)
{
const struct btf_field *f1 = a, *f2 = b;
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory
2026-08-25 9:49 ` [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory Khawar Ahemad
@ 2026-08-25 10:11 ` sashiko-bot
2026-08-25 10:22 ` Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
2 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-08-25 10:11 UTC (permalink / raw)
To: Khawar Ahemad; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Pages allocated via `__bpf_alloc_page()` in non-sleepable contexts are not zeroed, leading to a critical information leak. When `can_alloc_pages()` is false (e.g., in BPF programs running inside a BPF spinlock where IRQs are disabled, or under the spinlock in `arena_vm_fault`), the allocator falls back to `alloc_pages_nolock(__GFP_ACCOUNT, nid, 0)`. Because `__GFP_ZERO` is omitted and the page is never manually cleared, uninitialized physical memory is returned and subsequently mapped into the BPF arena. User space can then read this uninitialized kernel memory. (Note: The originally provided concern regarding missing dcache flushes on cache-aliasing architectures is a false positive because BPF arenas are only supported on hardware-coherent PIPT architectures like x86_64, arm64, riscv64, etc., via `bpf_jit_supports_arena()`).
--
--- Patch [1]: [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory ---
commit 49db9a147d01ab11180f1f75062c2ee24918b7a4
Author: Jiayuan Chen <jiayuan.chen@linux.dev>
bpf: Add a sleepable page allocator for map memory
This commit adds bpf_map_alloc_page_sleepable() for callers that are known to
be in a sleepable context. It allows them to use the normal blocking allocator
which can reclaim memory and engage the OOM machinery.
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -602,15 +602,14 @@ static bool can_alloc_pages(void)
> !IS_ENABLED(CONFIG_PREEMPT_RT);
> }
>
> +#define BPF_PAGE_GFP (GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT | __GFP_NOWARN)
> +
> static struct page *__bpf_alloc_page(int nid)
> {
> if (!can_alloc_pages())
> return alloc_pages_nolock(__GFP_ACCOUNT, nid, 0);
[Severity: Critical]
This is a pre-existing issue, but does missing __GFP_ZERO here cause an
information leak?
When can_alloc_pages() evaluates to false (e.g., in BPF programs running
inside a BPF spinlock where IRQs are disabled, or under the spinlock in
arena_vm_fault), the allocator falls back to alloc_pages_nolock() without
zeroing the page.
Because this uninitialized kernel memory can subsequently be mapped into the
BPF arena, user space could read it.
> - return alloc_pages_node(nid,
> - GFP_KERNEL | __GFP_ZERO | __GFP_ACCOUNT
> - | __GFP_NOWARN,
> - 0);
> + return alloc_pages_node(nid, BPF_PAGE_GFP, 0);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825094955.83240-1-ahemadkhawar123@gmail.com?part=1
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory
2026-08-25 9:49 ` [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory Khawar Ahemad
2026-08-25 10:11 ` sashiko-bot
@ 2026-08-25 10:22 ` Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
2 siblings, 0 replies; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 10:22 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf, jiayuan.chen
Hi,
Thanks for the careful review. After tracing the call path in full,
this finding is a false positive. Here is the evidence:
alloc_pages_nolock() is implemented as alloc_frozen_pages_nolock_noprof()
in mm/page_alloc.c. Regardless of which gfp_flags the caller passes, the
implementation unconditionally ORs in __GFP_ZERO before calling
get_page_from_freelist():
/* mm/page_alloc.c, alloc_frozen_pages_nolock_noprof() */
gfp_t alloc_gfp = __GFP_NOWARN | __GFP_ZERO | __GFP_NOMEMALLOC | __GFP_COMP
| gfp_flags;
The comment at that exact site makes the intent explicit:
"Specify __GFP_ZERO to make sure that call to kmsan_alloc_page() below
is safe in any context. Also zeroing the page is mandatory for
BPF use cases."
So even though __bpf_alloc_page() passes only __GFP_ACCOUNT to
alloc_pages_nolock(), the allocator enforces __GFP_ZERO unconditionally.
The returned page is always zeroed before being handed to the caller.
There is no path through which uninitialized kernel memory can be mapped
into a BPF arena.
The API documentation in include/linux/gfp.h also states that
__GFP_ACCOUNT is the only caller-supplied flag alloc_pages_nolock()
accepts, precisely because the implementation controls all others itself.
No code change is needed.
Khawar Ahemad <ahemadkhawar123@gmail.com>
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory
2026-08-25 9:49 ` [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory Khawar Ahemad
2026-08-25 10:11 ` sashiko-bot
2026-08-25 10:22 ` Khawar Ahemad
@ 2026-08-25 10:32 ` bot+bpf-ci
2 siblings, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-25 10:32 UTC (permalink / raw)
To: ahemadkhawar123, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2622 bytes --]
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index b3cd28d9e3b5..c817c99d2906 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -2784,6 +2784,7 @@ struct bpf_prog *bpf_prog_get_curr_or_next(u32 *id);
>
> int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
> unsigned long nr_pages, struct page **page_array);
> +struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map);
> #ifdef CONFIG_MEMCG
> void bpf_map_memcg_enter(const struct bpf_map *map, struct mem_cgroup **old_memcg,
> struct mem_cgroup **new_memcg);
[ ... ]
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 6874ba1424af..f9b81638e537 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
[ ... ]
> @@ -636,6 +635,20 @@ int bpf_map_alloc_pages(const struct bpf_map *map, int nid,
> return ret;
> }
>
> +/*
> + * For callers that know they run in a sleepable context, e.g. a user page
> + * fault handler. can_alloc_pages() is a conservative guess made for BPF
> + * program context - notably it is always false on PREEMPT_RT - so going
> + * through bpf_map_alloc_pages() there would needlessly pick the
> + * non-blocking allocator, which never reclaims and never engages the OOM
> + * machinery.
> + */
> +struct page *bpf_map_alloc_page_sleepable(const struct bpf_map *map)
> +{
> + might_sleep();
> + return alloc_pages_node(map->numa_node, BPF_PAGE_GFP, 0);
> +}
In v4 review, Michal Hocko asked how bpf_map_alloc_page_sleepable() achieves
safety from mm reentrancy, noting that sleepable context alone doesn't
guarantee safety. Kumar Kartikeya Dwivedi replied that it's only for the
arena fault handler in task context. Michal's follow-up was:
"So you rely on callers to know what they are doing. If that is the case
and generally acceptable by the BPF community (no real saying from me in
that matter) then make sure all that is properly documented. Because
sleepable context is not merely enough."
https://lore.kernel.org/bpf/aoxNTXWCCndiuewO@tiehlicka
Should the comment explain the reentrancy safety expectations? The current
documentation emphasizes PREEMPT_RT and the sleepable context, but doesn't
clarify when it's safe to call this from an mm reentrancy perspective, or
that the caller must ensure it's not in a context where the allocator might
reenter mm code that's already on the stack.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32835197153
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH bpf-next v6 2/4] bpf: arena: allocate the fault-in page outside the lock
2026-08-25 9:49 [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Khawar Ahemad
2026-08-25 9:49 ` [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory Khawar Ahemad
@ 2026-08-25 9:49 ` Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
2026-08-25 9:49 ` [PATCH bpf-next v6 3/4] selftests/bpf: Add read_cgroup_file() to cgroup_helpers Khawar Ahemad
` (2 subsequent siblings)
4 siblings, 1 reply; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 9:49 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123
From: Jiayuan Chen <jiayuan.chen@linux.dev>
arena_vm_fault() allocated the page while holding arena->spinlock, so it
could only use the non-blocking allocator. Once the memcg is at
memory.max that allocation just fails, the fault turns into
VM_FAULT_SIGSEGV, and the process gets a SIGSEGV on a perfectly valid
arena address. Hitting memory.max is routine (e.g. page cache from
reading a big file), so this kills innocent processes.
Rework the fault handler:
- Preallocate the page before taking the lock, like do_anonymous_page()
does, so it can sleep and go through reclaim and the memcg OOM killer,
instead of turning a routine memory.max into a fake segfault.
- On allocation failure return VM_FAULT_SIGBUS. The allocation already
ran reclaim and the OOM killer, so the failure is non-recoverable. For
a task faulting its own arena this changes nothing: the OOM killer
already picked it inside the allocation and it dies by SIGKILL, the
SIGBUS is shadowed by the pending fatal signal, and the memcg OOM is
still reported. VM_FAULT_OOM would instead be retried by the fault
path and can livelock when the charged memcg is not the faulting
task's (e.g. a shared arena) and its OOM killer cannot reach it.
- A lockless probe skips that preallocation when a page is already mapped
(e.g. allocated by the bpf program), so the common case wastes no
allocation. The rare race where such a page is freed before we take the
lock falls back to the non-blocking allocator under the lock.
- Return VM_FAULT_SIGBUS for the other non-recoverable errors (lock
failure, range-tree and page-table failures) instead of
VM_FAULT_SIGSEGV; only BPF_F_SEGV_ON_FAULT, and a scratch-page hole
under that flag, is a real user addressing error and keeps
VM_FAULT_SIGSEGV.
- Tidy up the error labels.
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Khawar Ahemad <ahemadkhawar123@gmail.com>
---
kernel/bpf/arena.c | 90 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 69 insertions(+), 21 deletions(-)
diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
index 7b6847200b..fa462a0ff1 100644
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@ -481,7 +481,8 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
struct bpf_map *map = vmf->vma->vm_file->private_data;
struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
struct mem_cgroup *new_memcg, *old_memcg;
- struct page *page;
+ struct page *page, *new_page = NULL;
+ vm_fault_t fault_ret;
long kbase, kaddr;
unsigned long flags;
int ret;
@@ -489,59 +490,106 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
kbase = bpf_arena_get_kern_vm_start(arena);
kaddr = kbase + (u32)(vmf->address);
- if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
+ page = vmalloc_to_page((void *)kaddr);
+ if (!page && !(arena->map.map_flags & BPF_F_SEGV_ON_FAULT)) {
+ /*
+ * Preallocate outside the lock with a sleepable allocator so it
+ * can reclaim and run the memcg OOM killer, which the
+ * non-blocking allocator under arena->spinlock cannot. A NULL
+ * return is non-recoverable, so fail with VM_FAULT_SIGBUS;
+ * VM_FAULT_OOM would be retried by the fault path and can
+ * livelock when the charged memcg is not the faulting task's.
+ */
+ bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
+ new_page = bpf_map_alloc_page_sleepable(map);
+ bpf_map_memcg_exit(old_memcg, new_memcg);
+ if (!new_page)
+ return VM_FAULT_SIGBUS;
+ }
+
+ if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
/*
* A failed lock means a possible deadlock was detected. Don't
* return VM_FAULT_RETRY: this handler never took mmap_lock, but
* the fault path would re-take it on retry and deadlock. Fail.
*/
+ if (new_page)
+ free_pages_nolock(new_page, 0);
return VM_FAULT_SIGBUS;
+ }
page = vmalloc_to_page((void *)kaddr);
if (page) {
- if (page == arena->scratch_page)
- /* BPF triggered scratch here; don't lazy-alloc over it */
- goto out_sigsegv;
+ if (page == arena->scratch_page) {
+ /*
+ * A scratch page marks a hole. Segfault only if the user
+ * asked for it; otherwise we could lazy-allocate but
+ * choose not to over a hole, so report a bus error.
+ */
+ fault_ret = (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) ?
+ VM_FAULT_SIGSEGV : VM_FAULT_SIGBUS;
+ goto out_err_locked;
+ }
/* already have a page vmap-ed */
goto out;
}
+ if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT) {
+ /* User space requested to segfault when page is not allocated by bpf prog */
+ fault_ret = VM_FAULT_SIGSEGV;
+ goto out_err_locked;
+ }
+
bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
- if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
- /* User space requested to segfault when page is not allocated by bpf prog */
- goto out_sigsegv_memcg;
+ if (!new_page) {
+ /*
+ * Very rare race: the bpf program had allocated a page here, so
+ * the lockless probe saw it and we skipped preallocation, but it
+ * freed the page before we took the lock. Now we do need one;
+ * sleeping is not allowed here, so fall back to the non-blocking
+ * allocator and give up if it fails.
+ */
+ ret = bpf_map_alloc_pages(map, map->numa_node, 1, &new_page);
+ if (ret) {
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
+ }
+ }
ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
- if (ret)
- goto out_sigsegv_memcg;
-
- struct apply_range_data data = { .arena = arena, .pages = &page, .i = 0 };
- /* Account into memcg of the process that created bpf_arena */
- ret = bpf_map_alloc_pages(map, NUMA_NO_NODE, 1, &page);
if (ret) {
- range_tree_set(&arena->rt, vmf->pgoff, 1);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
+ struct apply_range_data data = { .arena = arena, .pages = &new_page, .i = 0 };
ret = apply_to_page_range(&init_mm, kaddr, PAGE_SIZE, apply_range_set_cb, &data);
if (ret) {
range_tree_set(&arena->rt, vmf->pgoff, 1);
- free_pages_nolock(page, 0);
- goto out_sigsegv_memcg;
+ fault_ret = VM_FAULT_SIGBUS;
+ goto out_err_locked_memcg;
}
flush_vmap_cache(kaddr, PAGE_SIZE);
bpf_map_memcg_exit(old_memcg, new_memcg);
+ /* new_page was consumed */
+ page = new_page;
+ new_page = NULL;
out:
page_ref_add(page, 1);
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
+ if (new_page)
+ free_pages_nolock(new_page, 0);
vmf->page = page;
return 0;
-out_sigsegv_memcg:
+
+out_err_locked_memcg:
bpf_map_memcg_exit(old_memcg, new_memcg);
-out_sigsegv:
+out_err_locked:
raw_res_spin_unlock_irqrestore(&arena->spinlock, flags);
- return VM_FAULT_SIGSEGV;
+ if (new_page)
+ free_pages_nolock(new_page, 0);
+ return fault_ret;
}
static const struct vm_operations_struct arena_vm_ops = {
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 2/4] bpf: arena: allocate the fault-in page outside the lock
2026-08-25 9:49 ` [PATCH bpf-next v6 2/4] bpf: arena: allocate the fault-in page outside the lock Khawar Ahemad
@ 2026-08-25 10:32 ` bot+bpf-ci
0 siblings, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-25 10:32 UTC (permalink / raw)
To: ahemadkhawar123, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4069 bytes --]
> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
> index 7b6847200b43..fa462a0ff1ad 100644
> --- a/kernel/bpf/arena.c
> +++ b/kernel/bpf/arena.c
> @@ -481,7 +481,8 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
> struct bpf_map *map = vmf->vma->vm_file->private_data;
> struct bpf_arena *arena = container_of(map, struct bpf_arena, map);
> struct mem_cgroup *new_memcg, *old_memcg;
> - struct page *page;
> + struct page *page, *new_page = NULL;
> + vm_fault_t fault_ret;
> long kbase, kaddr;
> unsigned long flags;
> int ret;
> @@ -489,59 +490,106 @@ static vm_fault_t arena_vm_fault(struct vm_fault *vmf)
> kbase = bpf_arena_get_kern_vm_start(arena);
> kaddr = kbase + (u32)(vmf->address);
>
> - if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
> + page = vmalloc_to_page((void *)kaddr);
> + if (!page && !(arena->map.map_flags & BPF_F_SEGV_ON_FAULT)) {
> + /*
> + * Preallocate outside the lock with a sleepable allocator so it
> + * can reclaim and run the memcg OOM killer, which the
> + * non-blocking allocator under arena->spinlock cannot. A NULL
> + * return is non-recoverable, so fail with VM_FAULT_SIGBUS;
> + * VM_FAULT_OOM would be retried by the fault path and can
> + * livelock when the charged memcg is not the faulting task's.
> + */
> + bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
> + new_page = bpf_map_alloc_page_sleepable(map);
> + bpf_map_memcg_exit(old_memcg, new_memcg);
> + if (!new_page)
> + return VM_FAULT_SIGBUS;
> + }
> +
> + if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
A subsystem pattern flags this as potentially concerning: the lockless
probe at vmalloc_to_page() reads the arena's kernel page table before
arena->spinlock is taken. The result of this probe decides whether to
preallocate with the sleepable allocator. Could the probe's observation
go stale between the check and the lock acquisition?
Looking at the code flow, if the probe sees a mapped page but that page
is freed (by arena_free_pages(), arena_free_worker(), or a concurrent
bpf_arena_free_pages() clearing a scratch PTE) before the lock is taken,
the code reaches the fallback path:
> + if (!new_page) {
> + /*
> + * Very rare race: the bpf program had allocated a page here, so
> + * the lockless probe saw it and we skipped preallocation, but it
> + * freed the page before we took the lock. Now we do need one;
> + * sleeping is not allowed here, so fall back to the non-blocking
> + * allocator and give up if it fails.
> + */
> + ret = bpf_map_alloc_pages(map, map->numa_node, 1, &new_page);
> + if (ret) {
> + fault_ret = VM_FAULT_SIGBUS;
> + goto out_err_locked_memcg;
> + }
> + }
This fallback uses the non-blocking allocator inside arena_vm_fault()
where IRQs are disabled. In bpf_map_alloc_pages() -> __bpf_alloc_page()
-> can_alloc_pages() (kernel/bpf/syscall.c), the IRQ-disabled context
means the non-blocking path is taken with alloc_pages_nolock().
Doesn't this reintroduce the exact problem the patch aims to solve? When
the memcg is at memory.max, the non-blocking allocator returns NULL,
which turns into VM_FAULT_SIGBUS on a valid arena address. The commit
message says the patch prevents "a routine memory.max into a fake
segfault," but this race window leaves that scenario possible when:
(a) the lockless probe sees a bpf-program page and arena_free_pages() or
arena_free_worker() clears the PTE before the lock is taken, or
(b) the lockless probe sees arena->scratch_page and
apply_range_clear_cb() in bpf_arena_free_pages() clears that PTE (it
does ptep_get_and_clear() unconditionally and only skips freeing the
scratch page itself).
Is there a reason the lockless probe cannot be moved inside the locked
region, or is this residual race acceptable for the optimization?
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32835197153
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH bpf-next v6 3/4] selftests/bpf: Add read_cgroup_file() to cgroup_helpers
2026-08-25 9:49 [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Khawar Ahemad
2026-08-25 9:49 ` [PATCH bpf-next v6 1/4] bpf: Add a sleepable page allocator for map memory Khawar Ahemad
2026-08-25 9:49 ` [PATCH bpf-next v6 2/4] bpf: arena: allocate the fault-in page outside the lock Khawar Ahemad
@ 2026-08-25 9:49 ` Khawar Ahemad
2026-08-25 9:49 ` [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max Khawar Ahemad
2026-08-25 9:55 ` [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
4 siblings, 0 replies; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 9:49 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123
From: Jiayuan Chen <jiayuan.chen@linux.dev>
cgroup_helpers has write_cgroup_file()/write_cgroup_file_parent() but no
read counterpart. Add read_cgroup_file() and read_cgroup_file_parent() so
a forked child can read a cgroup file (e.g. memory.current) from the work
dir owned by the parent that set the environment up, without hand-building
the /mnt/... path.
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Khawar Ahemad <ahemadkhawar123@gmail.com>
---
tools/testing/selftests/bpf/cgroup_helpers.c | 67 ++++++++++++++++++++
tools/testing/selftests/bpf/cgroup_helpers.h | 4 ++
2 files changed, 71 insertions(+)
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.c b/tools/testing/selftests/bpf/cgroup_helpers.c
index 45cd0b479f..4183ff6150 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.c
+++ b/tools/testing/selftests/bpf/cgroup_helpers.c
@@ -188,6 +188,73 @@ int write_cgroup_file_parent(const char *relative_path, const char *file,
return __write_cgroup_file(cgroup_path, file, buf);
}
+static int __read_cgroup_file(const char *cgroup_path, const char *file,
+ char *buf, size_t len)
+{
+ char file_path[PATH_MAX + 1];
+ ssize_t got;
+ int fd;
+
+ snprintf(file_path, sizeof(file_path), "%s/%s", cgroup_path, file);
+ fd = open(file_path, O_RDONLY);
+ if (fd < 0) {
+ log_err("Opening %s", file_path);
+ return 1;
+ }
+
+ got = read(fd, buf, len - 1);
+ if (got < 0) {
+ log_err("Reading %s", file_path);
+ close(fd);
+ return 1;
+ }
+ buf[got] = '\0';
+ close(fd);
+ return 0;
+}
+
+/**
+ * read_cgroup_file() - Read from a cgroup file
+ * @relative_path: The cgroup path, relative to the workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
+/**
+ * read_cgroup_file_parent() - Read from a cgroup file in the parent process
+ * workdir
+ * @relative_path: The cgroup path, relative to the parent process workdir
+ * @file: The name of the file in cgroupfs to read from
+ * @buf: Buffer to read into, NUL-terminated on success
+ * @len: Size of @buf
+ *
+ * Read from a file in the given cgroup's directory under the parent process
+ * workdir.
+ *
+ * If successful, 0 is returned.
+ */
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len)
+{
+ char cgroup_path[PATH_MAX - 24];
+
+ format_parent_cgroup_path(cgroup_path, relative_path);
+ return __read_cgroup_file(cgroup_path, file, buf, len);
+}
+
/**
* setup_cgroup_environment() - Setup the cgroup environment
*
diff --git a/tools/testing/selftests/bpf/cgroup_helpers.h b/tools/testing/selftests/bpf/cgroup_helpers.h
index 3857304be8..d42d2e1304 100644
--- a/tools/testing/selftests/bpf/cgroup_helpers.h
+++ b/tools/testing/selftests/bpf/cgroup_helpers.h
@@ -15,6 +15,10 @@ int write_cgroup_file(const char *relative_path, const char *file,
const char *buf);
int write_cgroup_file_parent(const char *relative_path, const char *file,
const char *buf);
+int read_cgroup_file(const char *relative_path, const char *file,
+ char *buf, size_t len);
+int read_cgroup_file_parent(const char *relative_path, const char *file,
+ char *buf, size_t len);
int cgroup_setup_and_join(const char *relative_path);
int get_root_cgroup(void);
int create_and_get_cgroup(const char *relative_path);
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 9:49 [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Khawar Ahemad
` (2 preceding siblings ...)
2026-08-25 9:49 ` [PATCH bpf-next v6 3/4] selftests/bpf: Add read_cgroup_file() to cgroup_helpers Khawar Ahemad
@ 2026-08-25 9:49 ` Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
` (2 more replies)
2026-08-25 9:55 ` [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
4 siblings, 3 replies; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 9:49 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123
From: Jiayuan Chen <jiayuan.chen@linux.dev>
A child joins a memcg capped 64M above its post-load usage and faults an
arena in until it runs out of that budget.
With the fix the arena page comes from the sleepable allocator, so
hitting memory.max goes through the memcg OOM path and the child is
OOM-killed, which the test checks via memory.events "oom_kill".
Without the fix the test may still pass, because a concurrent blocking
allocation in the child (e.g. a COW fault on an inherited page) can hit
memory.max and OOM-kill it first. The goal is only that the fixed kernel
passes reliably.
# test_progs -v -t arena_memcg
serial_test_arena_memcg:PASS:child killed by signal
serial_test_arena_memcg:PASS:memcg oom_kill
#5 arena_memcg:OK
# dmesg (the OOM comes from the arena sleepable allocation)
test_progs invoked oom-killer: gfp_mask=GFP_KERNEL_ACCOUNT|__GFP_ZERO
arena_vm_fault+0x4bc/0xad0
Memory cgroup out of memory: Killed process 473 (test_progs)
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Khawar Ahemad <ahemadkhawar123@gmail.com>
---
.../selftests/bpf/prog_tests/arena_memcg.c | 157 ++++++++++++++++++
.../testing/selftests/bpf/progs/arena_memcg.c | 24 +++
2 files changed, 181 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_memcg.c
create mode 100644 tools/testing/selftests/bpf/progs/arena_memcg.c
diff --git a/tools/testing/selftests/bpf/prog_tests/arena_memcg.c b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
new file mode 100644
index 0000000000..752d29f299
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <sys/user.h>
+#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */
+#define PAGE_SIZE getpagesize()
+#endif
+
+#include "cgroup_helpers.h"
+#include "arena_memcg.skel.h"
+
+#define CG_PATH "/arena_memcg"
+
+/* Budget the arena gets on top of whatever is already charged after load. */
+#define ARENA_BUDGET (64 * 1024 * 1024)
+
+static void dump_memcg(int (*rd)(const char *, const char *, char *, size_t))
+{
+ char buf[512];
+
+ /*
+ * memory.current reads 0 once the child has left the cgroup, so it only
+ * carries information when dumped from the live child; memory.peak and
+ * memory.events survive the child and tell the story either way.
+ */
+ if (!rd(CG_PATH, "memory.current", buf, sizeof(buf)))
+ fprintf(stderr, "memory.current: %s", buf);
+ if (!rd(CG_PATH, "memory.max", buf, sizeof(buf)))
+ fprintf(stderr, "memory.max: %s", buf);
+ if (!rd(CG_PATH, "memory.peak", buf, sizeof(buf)))
+ fprintf(stderr, "memory.peak: %s", buf);
+ if (!rd(CG_PATH, "memory.events", buf, sizeof(buf)))
+ fprintf(stderr, "memory.events:\n%s", buf);
+ fflush(stderr);
+}
+
+/* Read one key from a flat keyed cgroup file, e.g. "oom_kill" in memory.events. */
+static long cg_read_key(const char *cg, const char *file, const char *key)
+{
+ char buf[512], *p;
+
+ if (read_cgroup_file(cg, file, buf, sizeof(buf)))
+ return -1;
+ p = strstr(buf, key);
+ if (!p)
+ return -1;
+ return strtol(p + strlen(key), NULL, 10);
+}
+
+void serial_test_arena_memcg(void)
+{
+ int cgroup_fd = -1, status, err;
+ const long ps = PAGE_SIZE;
+ char buf[64];
+ pid_t pid;
+
+ err = setup_cgroup_environment();
+ if (!ASSERT_OK(err, "setup_cgroup_environment"))
+ return;
+
+ cgroup_fd = create_and_get_cgroup(CG_PATH);
+ if (!ASSERT_OK_FD(cgroup_fd, "create_and_get_cgroup"))
+ goto out;
+
+ /* No memory controller -> nothing to test. */
+ if (read_cgroup_file(CG_PATH, "memory.current", buf, sizeof(buf))) {
+ fprintf(stderr, "%s:SKIP:no memory controller\n", __func__);
+ test__skip();
+ goto out;
+ }
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto out;
+ if (pid == 0) {
+ struct arena_memcg *cskel;
+ __u32 i, npages;
+ char *base;
+ size_t sz;
+ long cur;
+
+ /*
+ * Do everything from the child: the arena vma is VM_DONTCOPY so
+ * it would not survive fork(), only the child should be under the
+ * limit so that a memcg OOM cannot pick test_progs, and a map is
+ * charged to the memcg of the task that creates it - so join
+ * before load. The cgroup work dir belongs to the parent that set
+ * the environment up, so reach it with the _parent() helpers.
+ * Errors are reported to the parent through the exit code, since
+ * ASSERT_* in a forked child does not reach it.
+ */
+ snprintf(buf, sizeof(buf), "%d", getpid());
+ if (write_cgroup_file_parent(CG_PATH, "cgroup.procs", buf))
+ _exit(2);
+
+ cskel = arena_memcg__open_and_load();
+ if (!cskel)
+ _exit(3);
+
+ base = bpf_map__initial_value(cskel->maps.arena, &sz);
+ if (!base)
+ _exit(4);
+ npages = bpf_map__max_entries(cskel->maps.arena);
+
+ /*
+ * Cap only now, after load: everything but the fault-in is
+ * charged, so the arena gets a fixed budget regardless of what
+ * the load itself cost, and the load can never hit the limit.
+ */
+ if (read_cgroup_file_parent(CG_PATH, "memory.current", buf, sizeof(buf)))
+ _exit(5);
+ cur = strtol(buf, NULL, 10);
+ snprintf(buf, sizeof(buf), "%ld", cur + ARENA_BUDGET);
+ if (write_cgroup_file_parent(CG_PATH, "memory.max", buf))
+ _exit(6);
+
+ for (i = 0; i < npages; i++)
+ base[(size_t)i * ps] = 1;
+ /* Faulted everything without dying: dump why (only under -v). */
+ dump_memcg(read_cgroup_file_parent);
+ _exit(0);
+ }
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto out;
+
+ /* A non-zero exit means the child failed to set up; the code says where. */
+ if (WIFEXITED(status) && WEXITSTATUS(status)) {
+ ASSERT_OK(WEXITSTATUS(status), "child setup");
+ goto out;
+ }
+
+ /*
+ * Faulting a valid arena address until memory.max is hit must not look
+ * like an invalid access. Without the fix the fault path allocated with
+ * the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,
+ * and the child died with SIGSEGV on a valid address; now it is handled
+ * by the memcg OOM path instead. A SIGKILL alone would not prove the
+ * memcg OOM killer did it (a global OOM or an unrelated crash could also
+ * kill the child), so check memory.events.oom_kill, which records the
+ * memcg OOM and survives the child.
+ */
+ if (!ASSERT_TRUE(WIFSIGNALED(status), "child killed by signal"))
+ goto out;
+ if (!ASSERT_GE(cg_read_key(CG_PATH, "memory.events", "oom_kill"), 1,
+ "memcg oom_kill"))
+ dump_memcg(read_cgroup_file);
+out:
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ cleanup_cgroup_environment();
+}
diff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c
new file mode 100644
index 0000000000..88259cfea0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/arena_memcg.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_arena_common.h"
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARENA);
+ __uint(map_flags, BPF_F_MMAPABLE);
+ __uint(max_entries, 50000); /* number of pages */
+#ifdef __TARGET_ARCH_arm64
+ __ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
+#else
+ __ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
+#endif
+} arena SEC(".maps");
+
+SEC("syscall")
+int noop(void *ctx)
+{
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 9:49 ` [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max Khawar Ahemad
@ 2026-08-25 10:32 ` bot+bpf-ci
2026-08-25 10:43 ` Khawar Ahemad
2026-08-25 11:28 ` Khawar Ahemad
2 siblings, 0 replies; 17+ messages in thread
From: bot+bpf-ci @ 2026-08-25 10:32 UTC (permalink / raw)
To: ahemadkhawar123, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
ahemadkhawar123, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1338 bytes --]
> diff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c
> new file mode 100644
> index 000000000000..88259cfea06b
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/arena_memcg.c
> @@ -0,0 +1,24 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_arena_common.h"
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_ARENA);
> + __uint(map_flags, BPF_F_MMAPABLE);
> + __uint(max_entries, 50000); /* number of pages */
This isn't a bug, but could the 50000 carry a short note that it just has
to outrun ARENA_BUDGET on the smallest page size, so a later change to
either constant does not silently make the loop finish before memory.max
is reached?
> +#ifdef __TARGET_ARCH_arm64
> + __ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
> +#else
> + __ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
> +#endif
> +} arena SEC(".maps");
> +
> +SEC("syscall")
> +int noop(void *ctx)
> +{
> + return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32835197153
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 9:49 ` [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
@ 2026-08-25 10:43 ` Khawar Ahemad
2026-08-25 11:19 ` Jiayuan Chen
2026-08-25 11:28 ` Khawar Ahemad
2 siblings, 1 reply; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 10:43 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil,
martin.lau, yonghong.song, clm, ihor.solodrai, ahemadkhawar123
Addressing the three CI review comments on the v6 series:
---
[Patch 4/4] max_entries comment
---
> could the 50000 carry a short note that it just has to outrun
> ARENA_BUDGET on the smallest page size
Accepted. Will be fixed in v7 as a proper preceding block comment:
/*
* 50000 pages must exceed ARENA_BUDGET / PAGE_SIZE (64M / 4k = 16384)
* so the fault loop hits memory.max before exhausting the arena itself.
*/
__uint(max_entries, 50000);
---
[Patch 1/4] bpf_map_alloc_page_sleepable() reentrancy documentation
---
> Michal Hocko asked how bpf_map_alloc_page_sleepable() achieves safety
> from mm reentrancy, noting that sleepable context alone doesn't
> guarantee safety.
Accepted. Michal's concern is correct: "sleepable" does not automatically
imply "non-reentrant with respect to mm". The comment will be expanded in
v7 to document the reentrancy contract explicitly:
The only current caller is arena_vm_fault(), which is invoked from
handle_mm_fault() before taking arena->spinlock and before any BPF
subsystem lock. mmap_lock is held shared by the fault path, but the
allocator only needs it for vma lookup which it does not perform here.
No BPF-internal lock is held at the call site, so allocator reentrancy
through mm is not possible.
---
[Patch 2/4] Lockless probe race and non-blocking fallback
---
> Doesn't this reintroduce the exact problem the patch aims to solve?
No. The residual race is intentional, bounded, and qualitatively different
from the bug being fixed. Here is why:
The original bug was on the NORMAL, non-racy path: every arena fault where
no page existed would unconditionally use the non-blocking allocator, so
any routine memory.max event killed the process. The fix eliminates that
by preallocating with the sleepable allocator before the spinlock.
The fallback path (!new_page under the lock) is reached only when ALL of
the following are simultaneously true:
(a) The lockless probe saw a page (a BPF program allocated one).
(b) That page was freed between the probe and the lock acquisition.
(c) The resulting non-blocking allocation also fails (memory.max hit
in that same narrow window).
For (c) to occur independently of (a)+(b), the system must be under
memory pressure severe enough that a non-blocking atomic allocation fails
at the exact same moment the BPF program is freeing a page. A BPF program
that frees arena pages by definition had successfully allocated them
moments earlier, so available memory exists nearby. The likelihood of the
non-blocking allocator failing in this window is therefore extremely low.
More importantly, moving the probe inside the locked region is not
architecturally possible: the purpose of the probe is to decide WHETHER to
preallocate with the sleepable allocator. That decision must happen before
the lock is taken, because sleeping is not allowed inside the spinlock. The
probe-then-preallocate-then-lock sequence is the same pattern used by
do_anonymous_page() and do_cow_fault() in mm/memory.c.
On failure in the race path, VM_FAULT_SIGBUS is returned (not
VM_FAULT_SIGSEGV), which is the correct signal for a resource failure as
opposed to an addressing violation. The commit message's claim ("prevents
a routine memory.max into a fake segfault") refers to the normal path and
remains accurate.
Will add an additional sentence to the !new_page comment in v7 making
clear that this path cannot be sleepable by design.
Khawar Ahemad <ahemadkhawar123@gmail.com>
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 10:43 ` Khawar Ahemad
@ 2026-08-25 11:19 ` Jiayuan Chen
2026-08-25 12:23 ` Kumar Kartikeya Dwivedi
0 siblings, 1 reply; 17+ messages in thread
From: Jiayuan Chen @ 2026-08-25 11:19 UTC (permalink / raw)
To: Khawar Ahemad, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, emil, martin.lau,
yonghong.song, clm, ihor.solodrai, Kumar Kartikeya Dwivedi
On 8/25/26 6:43 PM, Khawar Ahemad wrote:
> Addressing the three CI review comments on the v6 series:
>
> ---
> [Patch 4/4] max_entries comment
> ---
>
>> could the 50000 carry a short note that it just has to outrun
>> ARENA_BUDGET on the smallest page size
> Accepted. Will be fixed in v7 as a proper preceding block comment:
>
> /*
> * 50000 pages must exceed ARENA_BUDGET / PAGE_SIZE (64M / 4k = 16384)
> * so the fault loop hits memory.max before exhausting the arena itself.
> */
> __uint(max_entries, 50000);
Not that simple.
It also make sure the arean not exceed 4G when page size is 64K.
---
Please don't submit further versions of this patchset. I have already
prepared a
V6 (the one you sent), and I'm planning to send it myself. Additional
submissions
from you would create unnecessary conflicts and confusion.
If you're interested in this series, I'd really appreciate your review
and feedback instead.
I'll also be sure to cc you on future versions.
Best regards
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 11:19 ` Jiayuan Chen
@ 2026-08-25 12:23 ` Kumar Kartikeya Dwivedi
0 siblings, 0 replies; 17+ messages in thread
From: Kumar Kartikeya Dwivedi @ 2026-08-25 12:23 UTC (permalink / raw)
To: Jiayuan Chen, Khawar Ahemad, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, emil, martin.lau,
yonghong.song, clm, ihor.solodrai
On Tue Aug 25, 2026 at 1:19 PM CEST, Jiayuan Chen wrote:
>
> On 8/25/26 6:43 PM, Khawar Ahemad wrote:
>> Addressing the three CI review comments on the v6 series:
>>
>> ---
>> [Patch 4/4] max_entries comment
>> ---
>>
>>> could the 50000 carry a short note that it just has to outrun
>>> ARENA_BUDGET on the smallest page size
>> Accepted. Will be fixed in v7 as a proper preceding block comment:
>>
>> /*
>> * 50000 pages must exceed ARENA_BUDGET / PAGE_SIZE (64M / 4k = 16384)
>> * so the fault loop hits memory.max before exhausting the arena itself.
>> */
>> __uint(max_entries, 50000);
>
>
> Not that simple.
>
> It also make sure the arean not exceed 4G when page size is 64K.
>
>
> ---
>
> Please don't submit further versions of this patchset. I have already
> prepared a
>
> V6 (the one you sent), and I'm planning to send it myself. Additional
> submissions
>
> from you would create unnecessary conflicts and confusion.
>
>
> If you're interested in this series, I'd really appreciate your review
> and feedback instead.
Khawar, it's not acceptable to take someone else's patchset, and then spam the
list multiple times without their permission. If you continue doing this we will
have no choice but to redirect all your emails to spam.
>
> I'll also be sure to cc you on future versions.
>
I think that is unnecessary, I don't think Khawar is providing any meaningful
input for this.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max
2026-08-25 9:49 ` [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max Khawar Ahemad
2026-08-25 10:32 ` bot+bpf-ci
2026-08-25 10:43 ` Khawar Ahemad
@ 2026-08-25 11:28 ` Khawar Ahemad
2 siblings, 0 replies; 17+ messages in thread
From: Khawar Ahemad @ 2026-08-25 11:28 UTC (permalink / raw)
To: jiayuan.chen
Cc: bpf, linux-kernel, ast, daniel, andrii, eddyz87, emil, martin.lau,
yonghong.song, clm, ihor.solodrai, memxor
Hi Jiayuan,
Thanks for the clarification, and that makes total sense regarding the
64KB page size (50000 * 64K = 3.05GB <= 4GB arena limit).
Apologies for stepping on your toes with the submission — I didn't mean
to create confusion or duplicate threads. I will definitely leave future
submissions to you.
Looking forward to your official posting, and I'd be happy to review and
test it whenever you send it out.
Thanks again,
Khawar Ahemad
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM
2026-08-25 9:49 [PATCH bpf-next v6 0/4] bpf: arena: handle memory.max on fault-in with reclaim/OOM Khawar Ahemad
` (3 preceding siblings ...)
2026-08-25 9:49 ` [PATCH bpf-next v6 4/4] selftests/bpf: Add a test for arena fault-in under memory.max Khawar Ahemad
@ 2026-08-25 9:55 ` Jiayuan Chen
4 siblings, 0 replies; 17+ messages in thread
From: Jiayuan Chen @ 2026-08-25 9:55 UTC (permalink / raw)
To: Khawar Ahemad, bpf
Cc: linux-kernel, ast, daniel, andrii, eddyz87, jiayuan.chen, emil
On 8/25/26 5:49 PM, Khawar Ahemad wrote:
> This series fixes an issue where accessing a valid BPF arena page under
> memory pressure (hitting a cgroup's memory.max limit) incorrectly results
> in a SIGSEGV crash instead of invoking memory reclaim or the memcg OOM
> killer.
>
> Problem:
Hi Khawar,
Just curious — is there a reason you're sending the same patchset
multiple times? It will cause flooding on the list.
^ permalink raw reply [flat|nested] 17+ messages in thread