* [PATCH bpf 0/2] bpf: Shorten pack_mutex critical sections @ 2026-11-03 14:28 Puranjay Mohan 2026-11-03 14:28 ` [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held Puranjay Mohan 2026-11-03 14:28 ` [PATCH bpf 2/2] bpf: Invalidate freed prog pack memory without holding pack_mutex Puranjay Mohan 0 siblings, 2 replies; 6+ messages in thread From: Puranjay Mohan @ 2026-11-03 14:28 UTC (permalink / raw) To: bpf Cc: Puranjay Mohan, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Mykyta Yatsenko Patch 2 moves bpf_arch_text_invalidate() out of pack_mutex, as suggested on v1 [1]. That makes the frees fast enough to hit a deadlock that was already there: the pack_mutex owner frees an empty pack and waits in flush_work() for vmalloc purge work, which cannot run while bpf_prog_free_deferred() items waiting for pack_mutex fill the system_percpu_wq pool. Patch 1 allocates and frees pack memory without pack_mutex. Seen on arm64; x86 and powerpc take the same path. Changes since v1: - Invalidate before taking pack_mutex on all archs, instead of dropping and retaking it on arm64 - New patch 1 for the deadlock [1] https://lore.kernel.org/bpf/20261024031046.625578-1-alexei.starovoitov@gmail.com/ Puranjay Mohan (2): bpf: Don't allocate and free prog pack memory with pack_mutex held bpf: Invalidate freed prog pack memory without holding pack_mutex kernel/bpf/core.c | 58 +++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 19 deletions(-) base-commit: 9abe2c42ecab6c86b54838d2d645ca2e10204606 -- 2.53.0-Meta ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held 2026-11-03 14:28 [PATCH bpf 0/2] bpf: Shorten pack_mutex critical sections Puranjay Mohan @ 2026-11-03 14:28 ` Puranjay Mohan 2026-11-03 15:20 ` bot+bpf-ci 2026-11-03 16:47 ` Alexei Starovoitov 2026-11-03 14:28 ` [PATCH bpf 2/2] bpf: Invalidate freed prog pack memory without holding pack_mutex Puranjay Mohan 1 sibling, 2 replies; 6+ messages in thread From: Puranjay Mohan @ 2026-11-03 14:28 UTC (permalink / raw) To: bpf Cc: Puranjay Mohan, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Mykyta Yatsenko bpf_prog_pack_free() frees an empty pack with pack_mutex held, and alloc_new_pack() calls set_memory_rox() with it held. Both can end up in __purge_vmap_area_lazy(), which queues purge_work on system_percpu_wq and waits for it since commit 72210662c5a2 ("mm: vmalloc: offload free_vmap_area_lock lock"). set_memory_rox() gets there through vm_unmap_aliases() on arm64, x86 and powerpc. Progs are freed from system_percpu_wq too, and bpf_prog_free_deferred() sleeps on pack_mutex. When enough progs are freed at once, these work items take all active slots of the pool that purge_work was queued to. purge_work never runs and everything waits for pack_mutex: INFO: task kworker/0:0:9 blocked for more than 20 seconds. Workqueue: events bpf_prog_free_deferred Call trace: ... mutex_lock+0x50/0x68 bpf_prog_pack_free+0x50/0x270 bpf_jit_binary_pack_free+0x28/0xb0 bpf_jit_free+0x58/0x1c8 bpf_prog_free_images+0x84/0xa0 bpf_prog_free_deferred+0x198/0x1d8 process_one_work+0x1a0/0x490 ... INFO: task kworker/0:0:9 is blocked on a mutex likely owned by task kworker/9:25:646. while kworker/9:25 is in: __flush_work+0x78/0xf8 flush_work+0x1c/0x30 __purge_vmap_area_lazy+0x420/0x470 _vm_unmap_aliases+0x21c/0x2b0 vfree+0x1d0/0x250 execmem_free+0x2c/0x58 bpf_prog_pack_free+0x1a8/0x270 bpf_jit_binary_pack_free+0x28/0xb0 bpf_jit_free+0x58/0x1c8 bpf_prog_free_images+0x84/0xa0 bpf_prog_free_deferred+0x198/0x1d8 process_one_work+0x1a0/0x490 This was hit in 16 cpu arm64 KVM guests by processes that attach cBPF filters to sockets and close them in a loop, with bpf_arch_text_invalidate() moved out of pack_mutex so that the frees are faster. pack_mutex protects pack_list, the bitmaps and arch_flush_needed. Set up a new pack without it, then look for room again, since another allocator may have added a pack in the meantime, and free the new pack if it is not needed. Free the memory of an empty pack after taking it off the list and dropping pack_mutex. Allocations larger than a pack touch none of this, so don't take pack_mutex for them. Fixes: 72210662c5a2 ("mm: vmalloc: offload free_vmap_area_lock lock") Signed-off-by: Puranjay Mohan <puranjay@kernel.org> --- kernel/bpf/core.c | 52 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 831f73f825993..4dac33a4208aa 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -951,7 +951,6 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins if (err) goto out; } - list_add_tail(&pack->list, &pack_list); return pack; out: @@ -963,11 +962,10 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic) { unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size); - struct bpf_prog_pack *pack, *fallback_pack = NULL; - unsigned long pos, fallback_pos = 0; + struct bpf_prog_pack *pack, *fallback_pack, *new_pack = NULL; + unsigned long pos, fallback_pos; void *ptr = NULL; - mutex_lock(&pack_mutex); if (size > BPF_PROG_PACK_SIZE) { /* * Allocations larger than a pack get their own pages, and @@ -991,8 +989,13 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool ptr = NULL; } } - goto out; + return ptr; } + + mutex_lock(&pack_mutex); +again: + fallback_pack = NULL; + fallback_pos = 0; list_for_each_entry(pack, &pack_list, list) { pos = bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0, nbits, 0); @@ -1021,11 +1024,21 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool goto found_free_area; } - pack = alloc_new_pack(bpf_fill_ill_insns); - if (!pack) - goto out; + if (new_pack) { + list_add_tail(&new_pack->list, &pack_list); + pack = new_pack; + new_pack = NULL; + pos = 0; + goto found_free_area; + } - pos = 0; + mutex_unlock(&pack_mutex); + new_pack = alloc_new_pack(bpf_fill_ill_insns); + if (!new_pack) + return NULL; + + mutex_lock(&pack_mutex); + goto again; found_free_area: /* Flush only for cBPF as it may contain a crafted gadget */ @@ -1040,9 +1053,12 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool } bitmap_set(pack->bitmap, pos, nbits); ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT); - -out: mutex_unlock(&pack_mutex); + + if (new_pack) { + bpf_jit_free_exec(new_pack->ptr); + kfree(new_pack); + } return ptr; } @@ -1052,12 +1068,12 @@ void bpf_prog_pack_free(void *ptr, u32 size) unsigned int nbits; unsigned long pos; - mutex_lock(&pack_mutex); if (size > BPF_PROG_PACK_SIZE) { bpf_jit_free_exec(ptr); - goto out; + return; } + mutex_lock(&pack_mutex); list_for_each_entry(tmp, &pack_list, list) { if (ptr >= tmp->ptr && (tmp->ptr + BPF_PROG_PACK_SIZE) > ptr) { pack = tmp; @@ -1079,13 +1095,17 @@ void bpf_prog_pack_free(void *ptr, u32 size) if (static_branch_unlikely(&bpf_pred_flush_enabled)) pack->arch_flush_needed = true; if (bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0, - BPF_PROG_CHUNK_COUNT, 0) == 0) { + BPF_PROG_CHUNK_COUNT, 0) == 0) list_del(&pack->list); + else + pack = NULL; +out: + mutex_unlock(&pack_mutex); + + if (pack) { bpf_jit_free_exec(pack->ptr); kfree(pack); } -out: - mutex_unlock(&pack_mutex); } static atomic_long_t bpf_jit_current; -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held 2026-11-03 14:28 ` [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held Puranjay Mohan @ 2026-11-03 15:20 ` bot+bpf-ci 2026-11-03 16:47 ` Alexei Starovoitov 1 sibling, 0 replies; 6+ messages in thread From: bot+bpf-ci @ 2026-11-03 15:20 UTC (permalink / raw) To: puranjay, bpf Cc: puranjay, ast, daniel, andrii, martin.lau, eddyz87, memxor, song, yonghong.song, mykyta.yatsenko5, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 4239 bytes --] > bpf: Don't allocate and free prog pack memory with pack_mutex held TL;DR: - bpf_prog_pack_alloc() may return NULL without rescanning pack_list if its unlocked alloc fails while another allocator added a pack, so JIT or trampoline allocation could spuriously fail - Commit message likely wrongly lists powerpc > bpf: Don't allocate and free prog pack memory with pack_mutex held > > bpf_prog_pack_free() frees an empty pack with pack_mutex held, and > alloc_new_pack() calls set_memory_rox() with it held. Both can end up in > __purge_vmap_area_lazy(), which queues purge_work on system_percpu_wq > and waits for it since commit 72210662c5a2 ("mm: vmalloc: offload > free_vmap_area_lock lock"). set_memory_rox() gets there through > vm_unmap_aliases() on arm64, x86 and powerpc. Is the powerpc part of this correct? On x86 and arm64 set_memory_rox() reaches vm_unmap_aliases() via change_page_attr_set_clr() and change_memory_common() respectively. On powerpc, set_memory_rox() is change_memory_attr(), which only calls apply_to_existing_page_range() with change_page_attr() and then flush_tlb_kernel_range(). The only vm_unmap_aliases() call under arch/powerpc is in the memory hot-remove path. On powerpc only the free path (vfree() -> vm_reset_perms() -> _vm_unmap_aliases()) seems to reach __purge_vmap_area_lazy(). Should powerpc be dropped from that sentence, or should it say which path applies? [ ... ] > diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c > --- a/kernel/bpf/core.c > +++ b/kernel/bpf/core.c [ ... ] > @@ -1021,11 +1024,21 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool > goto found_free_area; > } > > - pack = alloc_new_pack(bpf_fill_ill_insns); > - if (!pack) > - goto out; > + if (new_pack) { > + list_add_tail(&new_pack->list, &pack_list); > + pack = new_pack; > + new_pack = NULL; > + pos = 0; > + goto found_free_area; > + } > > - pos = 0; > + mutex_unlock(&pack_mutex); > + new_pack = alloc_new_pack(bpf_fill_ill_insns); > + if (!new_pack) > + return NULL; > + > + mutex_lock(&pack_mutex); > + goto again; Can this return NULL when another allocator has just added a pack with free space? The decision to allocate is made on a pack_list snapshot that is stale once pack_mutex is dropped. The success path re-checks pack_list after re-locking, but the failure path returns NULL right away. Suppose allocators A and B both find no room and both call alloc_new_pack() without the mutex. A succeeds and B fails, for example because execmem_vmalloc() cannot find space. On x86 the EXECMEM_BPF range is the module area, about 1GB shared with modules, with no fallback range. B returns NULL even though A's pack, added to pack_list moments later, has BPF_PROG_CHUNK_COUNT free chunks. Before this patch alloc_new_pack() ran under pack_mutex, so B would have waited for the mutex, found room in A's pack and succeeded without allocating. This also seems to make such failures more likely. Every concurrent allocator that sees no room now allocates its own BPF_PROG_PACK_SIZE (SZ_2M * num_possible_nodes()) region at the same time, before the extra ones are freed. On a large NUMA x86 host that transient demand could be many tens of MB to hundreds of MB of module space, for example when many seccomp or socket filters are JITed at once after the last pack was freed. The NULL reaches bpf_jit_binary_pack_alloc() and then the arch JITs (the x86 do_jit path does 'if (!header) goto out_addrs;'), so the program falls back to the interpreter or fails to load with CONFIG_BPF_JIT_ALWAYS_ON. Trampoline and struct_ops image allocation fail with -ENOMEM (bpf_tramp_image_alloc(), bpf_struct_ops_image_alloc()). Would it be better to re-take pack_mutex and scan pack_list once more when alloc_new_pack() fails, before returning NULL? The later commit 653557885326 ("bpf: Invalidate freed prog pack memory without holding pack_mutex") in the series does not touch this path. --- 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/41657960013 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held 2026-11-03 14:28 ` [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held Puranjay Mohan 2026-11-03 15:20 ` bot+bpf-ci @ 2026-11-03 16:47 ` Alexei Starovoitov 2026-11-05 18:27 ` Emil Tsalapatis 1 sibling, 1 reply; 6+ messages in thread From: Alexei Starovoitov @ 2026-11-03 16:47 UTC (permalink / raw) To: Puranjay Mohan, bpf Cc: Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Mykyta Yatsenko On Tue, Nov 03, 2026 at 06:28 AM Puranjay Mohan <puranjay@kernel.org> wrote: > Progs are freed from system_percpu_wq too, and bpf_prog_free_deferred() > sleeps on pack_mutex. When enough progs are freed at once, these work > items take all active slots of the pool that purge_work was queued to. > purge_work never runs and everything waits for pack_mutex: Thanks for taking over. Turned out Emil is fixing the same issue I was seeing: https://lore.kernel.org/bpf/20261102073439.6822-1-emil@etsalapatis.com/ Thousands of work items that sleep don't belong in system_percpu_wq. commit 117932eea99b fixed it for cgroup/bpf. On Emil's patch I proposed to move frees into their own wq with max_active = 1. this patch own wq ------------------------------------- ------------ ---------- work items per cpu that sleep on up to 1024 1 pack_mutex same deadlock on vmap_purge_lock still there gone N tasks find no room in the packs N new packs 1 new pack pack_mutex held over execmem calls no yes Row 1 also bounds the time that a prog load waits for pack_mutex. Row 2: the s390 JIT and the interpreter call vfree() of a VM_FLUSH_RESET_PERMS area for every prog. vm_reset_perms() calls _vm_unmap_aliases(), so these work items sleep on vmap_purge_lock. Its owner waits for purge_work in the same wq. Row 3: see below. Row 4 is fine. purge_work gets its slot, so the owner of pack_mutex makes progress. Then no need for the retry in bpf_prog_pack_alloc(). diff --git a/include/linux/bpf.h b/include/linux/bpf.h index fdbf638..6ce2e31 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -70,6 +70,7 @@ extern spinlock_t btf_idr_lock; extern struct kobject *btf_kobj; extern struct bpf_mem_alloc bpf_global_ma, bpf_global_percpu_ma; extern bool bpf_global_ma_set; +extern struct workqueue_struct *bpf_free_wq; typedef u64 (*bpf_callback_t)(u64, u64, u64, u64, u64); typedef int (*bpf_iter_init_seq_priv_t)(void *private_data, diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 831f73f..e83edea 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -3198,7 +3198,7 @@ static void bpf_prog_free_deferred(struct work_struct *work) /* The symbols are looked up under rcu_read_lock() */ INIT_RCU_WORK(&aux->rwork, bpf_prog_free_images); if (ksyms) - queue_rcu_work(system_percpu_wq, &aux->rwork); + queue_rcu_work(bpf_free_wq, &aux->rwork); else bpf_prog_free_images(&aux->rwork.work); } @@ -3211,9 +3211,26 @@ void bpf_prog_free(struct bpf_prog *fp) bpf_prog_put(aux->dst_prog); bpf_token_put(aux->token); INIT_WORK(&aux->work, bpf_prog_free_deferred); - schedule_work(&aux->work); + queue_work(bpf_free_wq, &aux->work); } +/* + * Many progs and trampolines can be freed at once and their work items sleep + * on pack_mutex. Its owner can wait for a work item of system_percpu_wq. + * Use a separate workqueue, so that they don't fill up max_active of + * system_percpu_wq. + */ +struct workqueue_struct *bpf_free_wq; + +static int __init bpf_free_wq_init(void) +{ + bpf_free_wq = alloc_workqueue("bpf_free", WQ_PERCPU, 1); + if (!bpf_free_wq) + panic("Failed to alloc workqueue for bpf free.\n"); + return 0; +} +pure_initcall(bpf_free_wq_init); + /* RNG for unprivileged user space with separated state from prandom_u32(). */ static DEFINE_PER_CPU(struct rnd_state, bpf_user_rnd_state); diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 21d4608..a34e8a2 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -584,7 +584,7 @@ static void __bpf_tramp_image_put_rcu(struct rcu_head *rcu) im = container_of(rcu, struct bpf_tramp_image, rcu); INIT_WORK(&im->work, __bpf_tramp_image_put_deferred); - schedule_work(&im->work); + queue_work(bpf_free_wq, &im->work); } /* callback, fexit step 2. Called after percpu_ref_kill confirms. */ > + mutex_unlock(&pack_mutex); > + new_pack = alloc_new_pack(bpf_fill_ill_insns); > + if (!new_pack) > + return NULL; > + > + mutex_lock(&pack_mutex); > + goto again; Every task that finds no room before task 1 adds its pack allocates one more: task 1 task 2 ... task N no room, unlock alloc_new_pack() no room, unlock alloc_new_pack() lock, list_add_tail() lock, room in the pack of task 1 unlock, free the new pack A pack is 2 Mbytes * num_possible_nodes(). Nobody is charged for it. N is the number of tasks, not cpus, since alloc_new_pack() sleeps. If we go with your approach the memory needs to be bound somehow. pw-bot: cr ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held 2026-11-03 16:47 ` Alexei Starovoitov @ 2026-11-05 18:27 ` Emil Tsalapatis 0 siblings, 0 replies; 6+ messages in thread From: Emil Tsalapatis @ 2026-11-05 18:27 UTC (permalink / raw) To: Alexei Starovoitov Cc: Puranjay Mohan, bpf, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Mykyta Yatsenko On Tue, Nov 3, 2026 at 4:54 PM Alexei Starovoitov <alexei.starovoitov@gmail.com> wrote: > > On Tue, Nov 03, 2026 at 06:28 AM Puranjay Mohan <puranjay@kernel.org> wrote: > > Progs are freed from system_percpu_wq too, and bpf_prog_free_deferred() > > sleeps on pack_mutex. When enough progs are freed at once, these work > > items take all active slots of the pool that purge_work was queued to. > > purge_work never runs and everything waits for pack_mutex: > > Thanks for taking over. > > Turned out Emil is fixing the same issue I was seeing: > https://lore.kernel.org/bpf/20261102073439.6822-1-emil@etsalapatis.com/ > > Thousands of work items that sleep don't belong in system_percpu_wq. > commit 117932eea99b fixed it for cgroup/bpf. > Sorry for the delay, I had v2 ready and hadn't sent it. I chatted with Puranjay and am sending it now. If we prefer the approach below he can take over instead. > On Emil's patch I proposed to move frees into their own wq > with max_active = 1. > > this patch own wq > ------------------------------------- ------------ ---------- > work items per cpu that sleep on up to 1024 1 > pack_mutex > same deadlock on vmap_purge_lock still there gone > N tasks find no room in the packs N new packs 1 new pack > pack_mutex held over execmem calls no yes > > Row 1 also bounds the time that a prog load waits for pack_mutex. > > Row 2: the s390 JIT and the interpreter call vfree() of > a VM_FLUSH_RESET_PERMS area for every prog. > vm_reset_perms() calls _vm_unmap_aliases(), so these work items sleep > on vmap_purge_lock. Its owner waits for purge_work in the same wq. > > Row 3: see below. > > Row 4 is fine. purge_work gets its slot, so the owner of pack_mutex > makes progress. > > Then no need for the retry in bpf_prog_pack_alloc(). > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h > index fdbf638..6ce2e31 100644 > --- a/include/linux/bpf.h > +++ b/include/linux/bpf.h > @@ -70,6 +70,7 @@ extern spinlock_t btf_idr_lock; > extern struct kobject *btf_kobj; > extern struct bpf_mem_alloc bpf_global_ma, bpf_global_percpu_ma; > extern bool bpf_global_ma_set; > +extern struct workqueue_struct *bpf_free_wq; > > typedef u64 (*bpf_callback_t)(u64, u64, u64, u64, u64); > typedef int (*bpf_iter_init_seq_priv_t)(void *private_data, > diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c > index 831f73f..e83edea 100644 > --- a/kernel/bpf/core.c > +++ b/kernel/bpf/core.c > @@ -3198,7 +3198,7 @@ static void bpf_prog_free_deferred(struct work_struct *work) > /* The symbols are looked up under rcu_read_lock() */ > INIT_RCU_WORK(&aux->rwork, bpf_prog_free_images); > if (ksyms) > - queue_rcu_work(system_percpu_wq, &aux->rwork); > + queue_rcu_work(bpf_free_wq, &aux->rwork); > else > bpf_prog_free_images(&aux->rwork.work); > } > @@ -3211,9 +3211,26 @@ void bpf_prog_free(struct bpf_prog *fp) > bpf_prog_put(aux->dst_prog); > bpf_token_put(aux->token); > INIT_WORK(&aux->work, bpf_prog_free_deferred); > - schedule_work(&aux->work); > + queue_work(bpf_free_wq, &aux->work); > } > > +/* > + * Many progs and trampolines can be freed at once and their work items sleep > + * on pack_mutex. Its owner can wait for a work item of system_percpu_wq. > + * Use a separate workqueue, so that they don't fill up max_active of > + * system_percpu_wq. > + */ > +struct workqueue_struct *bpf_free_wq; > + > +static int __init bpf_free_wq_init(void) > +{ > + bpf_free_wq = alloc_workqueue("bpf_free", WQ_PERCPU, 1); > + if (!bpf_free_wq) > + panic("Failed to alloc workqueue for bpf free.\n"); > + return 0; > +} > +pure_initcall(bpf_free_wq_init); > + > /* RNG for unprivileged user space with separated state from prandom_u32(). */ > static DEFINE_PER_CPU(struct rnd_state, bpf_user_rnd_state); > > diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c > index 21d4608..a34e8a2 100644 > --- a/kernel/bpf/trampoline.c > +++ b/kernel/bpf/trampoline.c > @@ -584,7 +584,7 @@ static void __bpf_tramp_image_put_rcu(struct rcu_head *rcu) > > im = container_of(rcu, struct bpf_tramp_image, rcu); > INIT_WORK(&im->work, __bpf_tramp_image_put_deferred); > - schedule_work(&im->work); > + queue_work(bpf_free_wq, &im->work); > } > > /* callback, fexit step 2. Called after percpu_ref_kill confirms. */ > > > + mutex_unlock(&pack_mutex); > > + new_pack = alloc_new_pack(bpf_fill_ill_insns); > > + if (!new_pack) > > + return NULL; > > + > > + mutex_lock(&pack_mutex); > > + goto again; > > Every task that finds no room before task 1 adds its pack allocates > one more: > > task 1 task 2 ... task N > no room, unlock > alloc_new_pack() no room, unlock > alloc_new_pack() > lock, list_add_tail() > lock, room in the pack of task 1 > unlock, free the new pack > > A pack is 2 Mbytes * num_possible_nodes(). Nobody is charged for it. > N is the number of tasks, not cpus, since alloc_new_pack() sleeps. > If we go with your approach the memory needs to be bound somehow. > > pw-bot: cr > ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf 2/2] bpf: Invalidate freed prog pack memory without holding pack_mutex 2026-11-03 14:28 [PATCH bpf 0/2] bpf: Shorten pack_mutex critical sections Puranjay Mohan 2026-11-03 14:28 ` [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held Puranjay Mohan @ 2026-11-03 14:28 ` Puranjay Mohan 1 sibling, 0 replies; 6+ messages in thread From: Puranjay Mohan @ 2026-11-03 14:28 UTC (permalink / raw) To: bpf Cc: Puranjay Mohan, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Mykyta Yatsenko bpf_prog_pack_free() calls bpf_arch_text_invalidate() with pack_mutex held. On arm64 it interrupts all other CPUs and waits for them: bpf_prog_free_deferred bpf_prog_free_images bpf_jit_free bpf_jit_binary_pack_free bpf_prog_pack_free bpf_arch_text_invalidate aarch64_insn_set __text_poke flush_icache_range kick_all_cpus_sync When many progs are freed at once, every prog load waits in bpf_prog_pack_alloc() for all of these broadcasts. On arm64 hosts with many CPUs this showed up as hung task reports with sk_attach_filter() blocked on pack_mutex for minutes. pack_mutex protects pack_list, the bitmaps and arch_flush_needed, not the text. Until bitmap_clear(), the bits of the range are set, so it cannot be allocated again and the pack cannot be freed. Invalidate it before taking pack_mutex. A ptr that is in no pack, which only a caller bug can pass, is now written to before the lookup warns about it. Tested in arm64 KVM guests with 16 and 64 cpus on a 72 cpu host. cpus/2 processes attach a cBPF filter to 256 sockets and close them in a loop for 20 sec. One more process attaches a filter every msec. Median of 5 runs. Sockets attached and closed in 20 sec, in millions (higher is better): before after 16 cpus 1.22 3.79 64 cpus 0.42 3.38 setsockopt(SO_ATTACH_FILTER) of the process that attaches every msec, in msec (lower is better): 16 cpus 64 cpus before after before after avg 1.32 0.27 7.21 0.56 p99 5.39 5.52 79.7 9.89 max 447 159 1598 253 Fixes: 1dad391daef1 ("bpf, arm64: use bpf_prog_pack for memory management") Suggested-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: Puranjay Mohan <puranjay@kernel.org> --- kernel/bpf/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 4dac33a4208aa..51f1b128f0d63 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -1073,6 +1073,9 @@ void bpf_prog_pack_free(void *ptr, u32 size) return; } + WARN_ONCE(bpf_arch_text_invalidate(ptr, size), + "bpf_prog_pack bug: missing bpf_arch_text_invalidate?\n"); + mutex_lock(&pack_mutex); list_for_each_entry(tmp, &pack_list, list) { if (ptr >= tmp->ptr && (tmp->ptr + BPF_PROG_PACK_SIZE) > ptr) { @@ -1087,9 +1090,6 @@ void bpf_prog_pack_free(void *ptr, u32 size) nbits = BPF_PROG_SIZE_TO_NBITS(size); pos = ((unsigned long)ptr - (unsigned long)pack->ptr) >> BPF_PROG_CHUNK_SHIFT; - WARN_ONCE(bpf_arch_text_invalidate(ptr, size), - "bpf_prog_pack bug: missing bpf_arch_text_invalidate?\n"); - bitmap_clear(pack->bitmap, pos, nbits); if (static_branch_unlikely(&bpf_pred_flush_enabled)) -- 2.53.0-Meta ^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-05 18:27 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-03 14:28 [PATCH bpf 0/2] bpf: Shorten pack_mutex critical sections Puranjay Mohan 2026-11-03 14:28 ` [PATCH bpf 1/2] bpf: Don't allocate and free prog pack memory with pack_mutex held Puranjay Mohan 2026-11-03 15:20 ` bot+bpf-ci 2026-11-03 16:47 ` Alexei Starovoitov 2026-11-05 18:27 ` Emil Tsalapatis 2026-11-03 14:28 ` [PATCH bpf 2/2] bpf: Invalidate freed prog pack memory without holding pack_mutex Puranjay Mohan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox