Skip to content

bpf: arena: handle memory.max on fault-in with reclaim/OOM - #13279

Open
kernel-patches-daemon-bpf[bot] wants to merge 1 commit into
bpf-net_basefrom
series/1142692=>bpf-net
Open

bpf: arena: handle memory.max on fault-in with reclaim/OOM#13279
kernel-patches-daemon-bpf[bot] wants to merge 1 commit into
bpf-net_basefrom
series/1142692=>bpf-net

Conversation

@kernel-patches-daemon-bpf

Copy link
Copy Markdown

Pull request for series with
subject: bpf: arena: handle memory.max on fault-in with reclaim/OOM
version: 3
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=1142692

@kernel-patches-daemon-bpf

Copy link
Copy Markdown
Author

Upstream branch: bfa3d89
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1142692
version: 3

Pull request is NOT updated. Failed to apply https://patchwork.kernel.org/project/netdevbpf/list/?series=1142692
error message:

Cmd('git') failed due to: exit code(128)
  cmdline: git am --3way
  stdout: 'Applying: bpf: Add a sleepable page allocator for map memory
Applying: bpf: arena: allocate the fault-in page outside the lock
Using index info to reconstruct a base tree...
M	kernel/bpf/arena.c
Falling back to patching base and 3-way merge...
Auto-merging kernel/bpf/arena.c
CONFLICT (content): Merge conflict in kernel/bpf/arena.c
Patch failed at 0002 bpf: arena: allocate the fault-in page outside the lock'
  stderr: 'error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"'

conflict:

diff --cc kernel/bpf/arena.c
index 49a8f7b1beef,09a718ca4c8b..000000000000
--- a/kernel/bpf/arena.c
+++ b/kernel/bpf/arena.c
@@@ -379,38 -490,87 +380,117 @@@ static vm_fault_t arena_vm_fault(struc
  	kbase = bpf_arena_get_kern_vm_start(arena);
  	kaddr = kbase + (u32)(vmf->address);
  
++<<<<<<< HEAD
 +	if (raw_res_spin_lock_irqsave(&arena->spinlock, flags))
 +		/* Make a reasonable effort to address impossible case */
 +		return VM_FAULT_RETRY;
 +
 +	page = vmalloc_to_page((void *)kaddr);
 +	if (page)
++=======
+ 	page = vmalloc_to_page((void *)kaddr);
+ 	if (!page && !(arena->map.map_flags & BPF_F_SEGV_ON_FAULT)) {
+ 		/*
+ 		 * We run in process context here, so preallocate the page
+ 		 * outside the lock with an explicitly sleepable allocator. It
+ 		 * can then go through reclaim (both memcg and global) and the
+ 		 * OOM path, the way do_anonymous_page() does; under
+ 		 * arena->spinlock only the non-blocking allocator is available,
+ 		 * which never reclaims. That also decides the return value:
+ 		 * VM_FAULT_OOM below is only meaningful if the OOM machinery was
+ 		 * actually engaged, which the non-blocking allocator never does.
+ 		 */
+ 		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_OOM;
+ 	}
+ 
+ 	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) {
+ 			/*
+ 			 * 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;
+ 		}
++>>>>>>> bpf: arena: allocate the fault-in page outside the lock
  		/* 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);
  
++<<<<<<< HEAD
 +	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;
 +
 +	ret = range_tree_clear(&arena->rt, vmf->pgoff, 1);
 +	if (ret)
 +		goto out_unlock_sigsegv;
 +
 +	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;
++=======
+ 	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) {
+ 		fault_ret = VM_FAULT_SIGBUS;
+ 		goto out_err_locked_memcg;
++>>>>>>> bpf: arena: allocate the fault-in page outside the lock
  	}
+ 	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);
++<<<<<<< HEAD
 +		free_pages_nolock(page, 0);
 +		goto out_unlock_sigsegv;
++=======
+ 		fault_ret = VM_FAULT_SIGBUS;
+ 		goto out_err_locked_memcg;
++>>>>>>> bpf: arena: allocate the fault-in page outside the lock
  	}
  	flush_vmap_cache(kaddr, PAGE_SIZE);
  	bpf_map_memcg_exit(old_memcg, new_memcg);
@@@ -417,12 -580,18 +500,23 @@@
  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;
++<<<<<<< HEAD
 +out_unlock_sigsegv:
 +	bpf_map_memcg_exit(old_memcg, new_memcg);
++=======
+ 
+ out_err_locked_memcg:
+ 	bpf_map_memcg_exit(old_memcg, new_memcg);
+ out_err_locked:
++>>>>>>> bpf: arena: allocate the fault-in page outside the lock
  	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 = {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants