Skip to content

Commit fd03439

Browse files
puranjaymohangregkh
authored andcommitted
bpf: switch task_vma iterator from mmap_lock to per-VMA locks
[ Upstream commit bee9ef4 ] The open-coded task_vma iterator holds mmap_lock for the entire duration of iteration, increasing contention on this highly contended lock. Switch to per-VMA locking. Find the next VMA via an RCU-protected maple tree walk and lock it with lock_vma_under_rcu(). lock_next_vma() is not used because its fallback takes mmap_read_lock(), and the iterator must work in non-sleepable contexts. lock_vma_under_rcu() is a point lookup (mas_walk) that finds the VMA containing a given address but cannot iterate across gaps. An RCU-protected vma_next() walk (mas_find) first locates the next VMA's vm_start to pass to lock_vma_under_rcu(). Between the RCU walk and the lock, the VMA may be removed, shrunk, or write-locked. On failure, advance past it using vm_end from the RCU walk. Because the VMA slab is SLAB_TYPESAFE_BY_RCU, vm_end may be stale; fall back to PAGE_SIZE advancement when it does not make forward progress. Concurrent VMA insertions at addresses already passed by the iterator are not detected. CONFIG_PER_VMA_LOCK is required; return -EOPNOTSUPP without it. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Link: https://lore.kernel.org/r/20260408154539.3832150-3-puranjay@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org> Stable-dep-of: 4cbee02 ("bpf: return VMA snapshot from task_vma iterator") Signed-off-by: Sasha Levin <sashal@kernel.org>
1 parent 239cec2 commit fd03439

1 file changed

Lines changed: 73 additions & 18 deletions

File tree

kernel/bpf/task_iter.c

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <linux/bpf_mem_alloc.h>
1111
#include <linux/btf_ids.h>
1212
#include <linux/mm_types.h>
13+
#include <linux/mmap_lock.h>
1314
#include <linux/sched/mm.h>
1415
#include "mmap_unlock_work.h"
1516

@@ -811,8 +812,8 @@ static inline void bpf_iter_mmput_async(struct mm_struct *mm)
811812
struct bpf_iter_task_vma_kern_data {
812813
struct task_struct *task;
813814
struct mm_struct *mm;
814-
struct mmap_unlock_irq_work *work;
815-
struct vma_iterator vmi;
815+
struct vm_area_struct *locked_vma;
816+
u64 next_addr;
816817
};
817818

818819
struct bpf_iter_task_vma {
@@ -833,21 +834,19 @@ __bpf_kfunc int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
833834
struct task_struct *task, u64 addr)
834835
{
835836
struct bpf_iter_task_vma_kern *kit = (void *)it;
836-
bool irq_work_busy = false;
837837
int err;
838838

839839
BUILD_BUG_ON(sizeof(struct bpf_iter_task_vma_kern) != sizeof(struct bpf_iter_task_vma));
840840
BUILD_BUG_ON(__alignof__(struct bpf_iter_task_vma_kern) != __alignof__(struct bpf_iter_task_vma));
841841

842-
/* bpf_iter_mmput_async() needs mmput_async() which requires CONFIG_MMU */
843-
if (!IS_ENABLED(CONFIG_MMU)) {
842+
if (!IS_ENABLED(CONFIG_PER_VMA_LOCK)) {
844843
kit->data = NULL;
845844
return -EOPNOTSUPP;
846845
}
847846

848847
/*
849848
* Reject irqs-disabled contexts including NMI. Operations used
850-
* by _next() and _destroy() (mmap_read_unlock, bpf_iter_mmput_async)
849+
* by _next() and _destroy() (vma_end_read, bpf_iter_mmput_async)
851850
* can take spinlocks with IRQs disabled (pi_lock, pool->lock).
852851
* Running from NMI or from a tracepoint that fires with those
853852
* locks held could deadlock.
@@ -890,18 +889,10 @@ __bpf_kfunc int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
890889
goto err_cleanup_iter;
891890
}
892891

893-
/* kit->data->work == NULL is valid after bpf_mmap_unlock_get_irq_work */
894-
irq_work_busy = bpf_mmap_unlock_get_irq_work(&kit->data->work);
895-
if (irq_work_busy || !mmap_read_trylock(kit->data->mm)) {
896-
err = -EBUSY;
897-
goto err_cleanup_mmget;
898-
}
899-
900-
vma_iter_init(&kit->data->vmi, kit->data->mm, addr);
892+
kit->data->locked_vma = NULL;
893+
kit->data->next_addr = addr;
901894
return 0;
902895

903-
err_cleanup_mmget:
904-
bpf_iter_mmput_async(kit->data->mm);
905896
err_cleanup_iter:
906897
put_task_struct(kit->data->task);
907898
bpf_mem_free(&bpf_global_ma, kit->data);
@@ -910,21 +901,85 @@ __bpf_kfunc int bpf_iter_task_vma_new(struct bpf_iter_task_vma *it,
910901
return err;
911902
}
912903

904+
/*
905+
* Find and lock the next VMA at or after data->next_addr.
906+
*
907+
* lock_vma_under_rcu() is a point lookup (mas_walk): it finds the VMA
908+
* containing a given address but cannot iterate. An RCU-protected
909+
* maple tree walk with vma_next() (mas_find) is needed first to locate
910+
* the next VMA's vm_start across any gap.
911+
*
912+
* Between the RCU walk and the lock, the VMA may be removed, shrunk,
913+
* or write-locked. On failure, advance past it using vm_end from the
914+
* RCU walk. SLAB_TYPESAFE_BY_RCU can make vm_end stale, so fall back
915+
* to PAGE_SIZE advancement to guarantee forward progress.
916+
*/
917+
static struct vm_area_struct *
918+
bpf_iter_task_vma_find_next(struct bpf_iter_task_vma_kern_data *data)
919+
{
920+
struct vm_area_struct *vma;
921+
struct vma_iterator vmi;
922+
unsigned long start, end;
923+
924+
retry:
925+
rcu_read_lock();
926+
vma_iter_init(&vmi, data->mm, data->next_addr);
927+
vma = vma_next(&vmi);
928+
if (!vma) {
929+
rcu_read_unlock();
930+
return NULL;
931+
}
932+
start = vma->vm_start;
933+
end = vma->vm_end;
934+
rcu_read_unlock();
935+
936+
vma = lock_vma_under_rcu(data->mm, start);
937+
if (!vma) {
938+
if (end <= data->next_addr)
939+
data->next_addr += PAGE_SIZE;
940+
else
941+
data->next_addr = end;
942+
goto retry;
943+
}
944+
945+
if (unlikely(vma->vm_end <= data->next_addr)) {
946+
data->next_addr += PAGE_SIZE;
947+
vma_end_read(vma);
948+
goto retry;
949+
}
950+
951+
return vma;
952+
}
953+
913954
__bpf_kfunc struct vm_area_struct *bpf_iter_task_vma_next(struct bpf_iter_task_vma *it)
914955
{
915956
struct bpf_iter_task_vma_kern *kit = (void *)it;
957+
struct vm_area_struct *vma;
916958

917959
if (!kit->data) /* bpf_iter_task_vma_new failed */
918960
return NULL;
919-
return vma_next(&kit->data->vmi);
961+
962+
if (kit->data->locked_vma) {
963+
vma_end_read(kit->data->locked_vma);
964+
kit->data->locked_vma = NULL;
965+
}
966+
967+
vma = bpf_iter_task_vma_find_next(kit->data);
968+
if (!vma)
969+
return NULL;
970+
971+
kit->data->locked_vma = vma;
972+
kit->data->next_addr = vma->vm_end;
973+
return vma;
920974
}
921975

922976
__bpf_kfunc void bpf_iter_task_vma_destroy(struct bpf_iter_task_vma *it)
923977
{
924978
struct bpf_iter_task_vma_kern *kit = (void *)it;
925979

926980
if (kit->data) {
927-
bpf_mmap_unlock_mm(kit->data->work, kit->data->mm);
981+
if (kit->data->locked_vma)
982+
vma_end_read(kit->data->locked_vma);
928983
put_task_struct(kit->data->task);
929984
bpf_iter_mmput_async(kit->data->mm);
930985
bpf_mem_free(&bpf_global_ma, kit->data);

0 commit comments

Comments
 (0)