From: Uladzislau Rezki <urezki@gmail.com>
To: sashiko-reviews@lists.linux.dev
Cc: "Uladzislau Rezki (Sony)" <urezki@gmail.com>,
linux-mm@kvack.org, Andrew Morton <akpm@linux-foundation.org>,
Baoquan He <baoquan.he@linux.dev>,
"David Hildenbrand (Arm)" <david@kernel.org>
Subject: Re: [PATCH] mm/vmalloc: Bail out if in softirq context
Date: Tue, 27 Oct 2026 14:14:45 +0100 [thread overview]
Message-ID: <auCjxUJEOMzmcoga@milan> (raw)
In-Reply-To: <sashiko-outbox-186354@kernel.org>
On Tue, Oct 27, 2026 at 10:25:23AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] mm/vmalloc: ABBA deadlock when calling vmalloc() with BH or IRQs disabled
> - [Medium] mm/vmalloc: __get_vm_area_node() irq bailout triggers warnings and retry
> --
>
> commit 9fea18279139941aa247a1a577dd5e101116871a
> Author: Uladzislau Rezki (Sony) <urezki@gmail.com>
>
> mm/vmalloc: Bail out if in softirq context
>
> This commit restores a softirq check in __get_vm_area_node() using
> in_serving_softirq() so that vmalloc() bails out when invoked from softirq
> context while still permitting callers with disabled bottom halves. It also
> replaces BUG_ON(in_nmi() || in_hardirq()) with a WARN_ON_ONCE() check that
> returns NULL.
>
> > diff --git a/mm/vmalloc.c b/mm/vmalloc.c
> > index 44e0eb32708f1..5a949525b9db8 100644
> > --- a/mm/vmalloc.c
> > +++ b/mm/vmalloc.c
> > @@ -3271,7 +3271,13 @@ struct vm_struct *__get_vm_area_node(unsigned long size,
> > struct vm_struct *area;
> > unsigned long requested_size = size;
> >
> > - BUG_ON(in_nmi() || in_hardirq());
> > + /* IRQ re-entrancy -> irq_exit() -> process softirq. */
> > + if (in_serving_softirq())
> > + return NULL;
> > +
> > + if (WARN_ON_ONCE(in_nmi() || in_hardirq()))
> > + return NULL;
>
> [Severity: High]
> Can allowing vmalloc() in task context with bottom halves or interrupts
> disabled still cause a multi-CPU ABBA deadlock on vmalloc's internal locks?
>
> In mm/vmalloc.c, alloc_vmap_area() acquires vn->pool_lock,
> free_vmap_area_lock, and vn->busy.lock (and __pte_alloc_kernel() in
> mm/memory.c acquires init_mm.page_table_lock) with plain spin_lock(), leaving
> bottom halves and hardware interrupts enabled:
>
> mm/vmalloc.c:alloc_vmap_area() {
> ...
> spin_lock(&vn->busy.lock);
> insert_vmap_area(va, &vn->busy.root, &vn->busy.head);
> spin_unlock(&vn->busy.lock);
> ...
> }
>
> Because __get_vm_area_node() only checks in_serving_softirq(), in_nmi(), and
> in_hardirq(), a task-context caller holding a lock via spin_lock_bh() (which
> sets SOFTIRQ_DISABLE_OFFSET rather than SOFTIRQ_OFFSET) or
> spin_lock_irqsave() (where irqs_disabled() is true while in_hardirq() is
> false) will proceed into alloc_vmap_area().
>
> Using the bridge FDB path referenced by commit 04aa71da5f35 ("mm/vmalloc: do
> not trigger BUG() on BH disabled context"):
>
> net/bridge/br_fdb.c:br_fdb_add_local() {
> ...
> spin_lock_bh(&br->hash_lock);
> ret = fdb_add_local(br, source, addr, vid);
> spin_unlock_bh(&br->hash_lock);
> ...
> }
>
> net/bridge/br_fdb.c:br_fdb_update() {
> ...
> } else {
> spin_lock(&br->hash_lock);
> fdb = fdb_create(br, source, addr, vid, flags);
> ...
> }
>
> If CPU 0 is in task context inside alloc_vmap_area() holding
> spin_lock(&vn->busy.lock) or spin_lock(&free_vmap_area_lock), and CPU 1 is in
> task context inside br_fdb_add_local() holding spin_lock_bh(&br->hash_lock):
>
> 1. A hardware interrupt arrives on CPU 0, and on irq_exit() NET_RX_SOFTIRQ
> runs br_fdb_update(), which spins on spin_lock(&br->hash_lock) before ever
> reaching fdb_create() or __get_vm_area_node().
>
> 2. CPU 1 proceeds through
> fdb_add_local() -> fdb_create() -> rhashtable_lookup_insert_fast() ->
> rhashtable_insert_rehash() -> bucket_table_alloc(GFP_ATOMIC | __GFP_NOWARN)
> -> kvmalloc() -> vmalloc() -> __get_vm_area_node().
> 3. Because in_serving_softirq() is false on CPU 1, it enters
> alloc_vmap_area() and spins on free_vmap_area_lock or vn->busy.lock held
> by CPU 0.
>
My patch fixes self-deadlock on re-entry.
<snip>
CPU 0:
task
vmalloc()
lock(X)
IRQ
irq_exit()
softirq
vmalloc()
lock(X) -> self-deadlock
<snip>
and we used to bail out in this case. So it just restores previous
behaviour which forbids re-entry.
> Similarly, a task-context caller holding a lock with spin_lock_irqsave() can
> enter alloc_vmap_area() with irqs_disabled() and invert lock ordering against
> any hardirq handler acquiring that same lock (and sleep with interrupts
> disabled on CONFIG_PREEMPT_RT).
>
> Should __get_vm_area_node() also bail out when in_softirq() or
> irqs_disabled() is true?
>
I think it is another patch:
if (irqs_disabled() && !early_boot_irqs_disabled)
return NULL;
> [Severity: Medium]
> When __get_vm_area_node() returns NULL due to in_serving_softirq(), in_nmi(),
> or in_hardirq(), how does __vmalloc_node_range_noprof() handle the NULL
> return?
>
> Looking at __vmalloc_node_range_noprof() in mm/vmalloc.c:
>
> mm/vmalloc.c:__vmalloc_node_range_noprof() {
> ...
> area = __get_vm_area_node(real_size, align, shift, VM_ALLOC |
> VM_UNINITIALIZED | vm_flags, start, end, node,
> gfp_mask, caller);
> if (!area) {
> bool nofail = gfp_mask & __GFP_NOFAIL;
> warn_alloc(gfp_mask, NULL,
> "vmalloc error: size %lu, align 0x%lx, vm_struct allocation failed%s",
> size, align, (nofail) ? ". Retrying." : "");
> if (nofail) {
> schedule_timeout_uninterruptible(1);
> goto again;
> }
> goto fail;
> }
> ...
> }
>
> Since commit c6307674ed82 ("mm: kvmalloc: add non-blocking support for
> vmalloc"), __kvmalloc_node_noprof() in mm/slub.c falls back to
> __vmalloc_node_range_noprof() for non-blocking allocations (such as
> GFP_ATOMIC or GFP_NOWAIT) when size > PAGE_SIZE and __do_kmalloc_node()
> fails, while kmalloc_gfp_adjust() only adds __GFP_NOWARN to the kmalloc
> attempt:
>
> mm/slub.c:__kvmalloc_node_noprof() {
> ...
> return __vmalloc_node_range_noprof(size, align, VMALLOC_START, VMALLOC_END,
> flags, PAGE_KERNEL, allow_block ? VM_ALLOW_HUGE_VMAP:0,
> node, __builtin_return_address(0));
> }
>
> Doesn't bailing out in __get_vm_area_node() lead to a few issues in callers:
>
> 1. In softirq, hardirq, or NMI context, when !area is reached in
> __vmalloc_node_range_noprof(), it unconditionally calls warn_alloc()
> whenever __GFP_NOWARN was not passed by the caller (and if shift >
> PAGE_SHIFT, goto fail resets shift = PAGE_SHIFT and jumps to goto again,
> repeating the call and warning a second time).
>
Calling vmalloc() from nmi, irq-context or on re-entry is a wrong behaviour.
> 2. If a caller in hardirq or NMI context invokes
> kvmalloc(size, GFP_ATOMIC | __GFP_NOWARN) (for example,
> rhashtable_insert_rehash() -> bucket_table_alloc() in lib/rhashtable.c,
> reachable from irq-safe rhashtable users and BPF_MAP_TYPE_RHASHTABLE
> updates in rhtab_map_update_elem() in kernel/bpf/hashtab.c) and the
> kmalloc attempt fails for size > PAGE_SIZE, __kvmalloc_node_noprof() falls
> back to __vmalloc_node_range_noprof() -> __get_vm_area_node() and triggers
> WARN_ON_ONCE(in_nmi() || in_hardirq()) even though the caller passed
> GFP_ATOMIC | __GFP_NOWARN and handles NULL.
>
Caller is not allowed to invoke vmalloc() from NMI or hard-irq context.
> 3. In __vmalloc_node_range_noprof(), nofail is not cleared when
> !gfpflags_allow_blocking(gfp_mask) (unlike in __vmalloc_area_node()), so
> if __GFP_NOFAIL is set when __get_vm_area_node() returns NULL in
> interrupt context, it will call schedule_timeout_uninterruptible(1) in
> atomic context and loop indefinitely at goto again.
>
__GFP_NOFAIL and NOWAIT or ATOMIC can not coexist together:
* %__GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller
* cannot handle allocation failures. The allocation could block
* indefinitely but will never return with failure. Testing for
* failure is pointless.
* It _must_ be blockable and used together with __GFP_DIRECT_RECLAIM.
--
Uladzislau Rezki
next prev parent reply other threads:[~2026-10-27 13:14 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 10:25 [PATCH] mm/vmalloc: Bail out if in softirq context Uladzislau Rezki (Sony)
2026-10-26 16:04 ` Andrew Morton
2026-10-26 17:39 ` Uladzislau Rezki
2026-10-27 0:57 ` Baoquan He
2026-10-27 10:25 ` sashiko-bot
2026-10-27 13:14 ` Uladzislau Rezki [this message]
2026-10-27 17:43 ` Uladzislau Rezki
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=auCjxUJEOMzmcoga@milan \
--to=urezki@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=baoquan.he@linux.dev \
--cc=david@kernel.org \
--cc=linux-mm@kvack.org \
--cc=sashiko-reviews@lists.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.