All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jiayuan Chen <jiayuan.chen@linux.dev>
To: Emil Tsalapatis <emil@etsalapatis.com>, bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	John Fastabend <john.fastabend@gmail.com>,
	Andrii Nakryiko <andrii@kernel.org>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Martin KaFai Lau <martin.lau@linux.dev>,
	Song Liu <song@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>,
	Jiri Olsa <jolsa@kernel.org>, Shuah Khan <shuah@kernel.org>,
	Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
	Clark Williams <clrkwllms@kernel.org>,
	Steven Rostedt <rostedt@goodmis.org>,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
	linux-rt-devel@lists.linux.dev
Subject: Re: [PATCH bpf-next 2/3] bpf: arena: allocate the fault-in page outside the lock
Date: Mon, 3 Aug 2026 14:43:18 +0800	[thread overview]
Message-ID: <354f214b-bd81-49b6-9642-ddfbaa936694@linux.dev> (raw)
In-Reply-To: <DK9T753AD314.1K18DTGBR7OYN@etsalapatis.com>


On 7/28/26 9:22 AM, Emil Tsalapatis wrote:
> On Mon Jul 27, 2026 at 2:24 AM EDT, Jiayuan Chen wrote:
>> 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.
>>
>> Preallocate the page before taking the lock, like do_anonymous_page()
>> does, so the allocation can sleep and go through reclaim and the OOM
>> path, and return VM_FAULT_OOM on failure so the memcg OOM handler runs
>> instead of a fake segfault. Also tidy up the error labels.
>>
>> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
>> ---
>>   kernel/bpf/arena.c | 83 +++++++++++++++++++++++++++++++++++-----------
>>   1 file changed, 63 insertions(+), 20 deletions(-)
>>
>> diff --git a/kernel/bpf/arena.c b/kernel/bpf/arena.c
>> index 34f023a537fe..22a41e3c53b8 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,55 +490,97 @@ 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) {
>> +		/*
>> +		 * Preallocate outside the lock so the allocation can sleep and go
>> +		 * through reclaim (both memcg and global), the way do_anonymous_page()
>> +		 * does. Under arena->spinlock only the non-blocking allocator is
>> +		 * available, which never reclaims.
>> +		 *
> Can you add here that we're in process context which is why we make an
> explicitly sleepable call? That also strengthens the explanation of why
> we want to preallocate in the first place.
>
>> +		 * This has to be the sleepable variant: VM_FAULT_OOM below is only
>> +		 * meaningful if the OOM machinery was actually engaged. A failure
>> +		 * from the non-blocking allocator engages nothing, so the fault
>> +		 * would be retried forever.
>> +		 */
>> +		bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
>> +		new_page = bpf_map_alloc_page_sleepable(map, NUMA_NO_NODE);
>> +		bpf_map_memcg_exit(old_memcg, new_memcg);
>> +		if (!new_page)
>> +			return VM_FAULT_OOM;
>> +	}
>> +
>> +	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags)) {
>>   		/* Make a reasonable effort to address impossible case */
>> -		return VM_FAULT_RETRY;
>> +		fault_ret = VM_FAULT_RETRY;
>> +		goto out_err;
>> +	}
>>   
>>   	page = vmalloc_to_page((void *)kaddr);
>>   	if (page) {
>> -		if (page == arena->scratch_page)
>> +		if (page == arena->scratch_page) {
>>   			/* BPF triggered scratch here; don't lazy-alloc over it */
>> -			goto out_sigsegv;
>> +			fault_ret = VM_FAULT_SIGSEGV;
>> +			goto out_err_locked;
>> +		}
>>   		/* already have a page vmap-ed */
>>   		goto out;
>>   	}
>>   
>> +	/*
>> +	 * The lockless probe was racy: it saw a page, so nothing was
>> +	 * preallocated, but the re-check under the lock finds it gone - a
>> +	 * concurrent free must have run in between. There is nothing to
>> +	 * install and we cannot allocate under the lock, so retry the fault
>> +	 * and preallocate next time.
>> +	 */
>> +	if (!new_page) {
>> +		fault_ret = VM_FAULT_RETRY;
>> +		goto out_err_locked;
>> +	}
>> +
>>   	bpf_map_memcg_enter(&arena->map, &old_memcg, &new_memcg);
>>   
>> -	if (arena->map.map_flags & BPF_F_SEGV_ON_FAULT)
>> +	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;
>> +		fault_ret = VM_FAULT_SIGSEGV;
>> +		goto out_err_locked_memcg;
>> +	}
> Let's hoist this further up, I don't see why we're waiting to get so
> much into handling the fault before checking if we're even allowed to
> do it in the first place.
>
>>   
>>   	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_OOM;
> Can you add a comment that VM_FAULT_OOM is because the only way clearing
> the tree can fail is if there is a failed range allocation?
>
>> +		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_SIGSEGV;
>> +		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;
>> +out_err:
>> +	if (new_page)
>> +		free_pages_nolock(new_page, 0);
>> +	return fault_ret;
> The out_err label is used only once. Can we replace the label with
> the code itself, even if this means some mild duplication? Four
> different labels are too many imo.
>
>>   }
>>   
>>   static const struct vm_operations_struct arena_vm_ops = {
> pw-bot: cr



Sorry for the delay. I missed this mail earlier...

Everything makes sense.


  reply	other threads:[~2026-08-03  6:43 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27  6:24 [PATCH bpf-next 0/3] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
2026-07-27  6:24 ` [PATCH bpf-next 1/3] bpf: Add a sleepable page allocator for map memory Jiayuan Chen
2026-07-27  6:37   ` sashiko-bot
2026-07-27  7:30     ` Jiayuan Chen
2026-07-28  0:48   ` Emil Tsalapatis
2026-07-27  6:24 ` [PATCH bpf-next 2/3] bpf: arena: allocate the fault-in page outside the lock Jiayuan Chen
2026-07-27  6:42   ` sashiko-bot
2026-07-27  8:00     ` Jiayuan Chen
2026-07-28  1:00       ` Emil Tsalapatis
2026-07-27  7:10   ` bot+bpf-ci
2026-07-28  1:22   ` Emil Tsalapatis
2026-08-03  6:43     ` Jiayuan Chen [this message]
2026-07-27  6:24 ` [PATCH bpf-next 3/3] selftests/bpf: Add a test for arena fault-in under memory.max Jiayuan Chen
2026-07-28  0:54   ` Emil Tsalapatis
2026-08-03 11:42     ` Jiayuan Chen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=354f214b-bd81-49b6-9642-ddfbaa936694@linux.dev \
    --to=jiayuan.chen@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bigeasy@linutronix.de \
    --cc=bpf@vger.kernel.org \
    --cc=clrkwllms@kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-rt-devel@lists.linux.dev \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=rostedt@goodmis.org \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.