]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mm/page_alloc: return NULL early from alloc_frozen_pages_nolock() in NMI on UP
authorHarry Yoo (Oracle) <harry@kernel.org>
Mon, 27 Apr 2026 07:09:52 +0000 (16:09 +0900)
committerVlastimil Babka (SUSE) <vbabka@kernel.org>
Mon, 27 Apr 2026 07:14:36 +0000 (09:14 +0200)
On UP kernels (!CONFIG_SMP), spin_trylock() is a no-op that
unconditionally succeeds even when the lock is already held. As a
result, alloc_frozen_pages_nolock() called from NMI context can
re-enter rmqueue() and acquire the zone lock that the interrupted
context is already holding, corrupting the freelists.

With CONFIG_DEBUG_SPINLOCK on UP, the following BUG is triggered with
the slub_kunit test module:

  BUG: spinlock trylock failure on UP on CPU#0, kunit_try_catch/243
  [...]
  Call Trace:
   <NMI>
   dump_stack_lvl+0x3f/0x60
   do_raw_spin_trylock+0x41/0x50
   _raw_spin_trylock+0x24/0x50
   rmqueue.isra.0+0x2a9/0xa70
   get_page_from_freelist+0xeb/0x450
   alloc_frozen_pages_nolock_noprof+0x111/0x1e0
   allocate_slab+0x42a/0x500
   ___slab_alloc+0xa7/0x4c0
   kmalloc_nolock_noprof+0x164/0x310
   [...]
   </NMI>

Fix this by returning NULL early when invoked from NMI on a UP kernel.

Link: https://lore.kernel.org/linux-mm/ad_cqe51pvr1WaDg@hyeyoo
Cc: stable@vger.kernel.org
Fixes: d7242af86434 ("mm: Introduce alloc_frozen_pages_nolock()")
Signed-off-by: Harry Yoo (Oracle) <harry@kernel.org>
Link: https://patch.msgid.link/20260427-nolock-api-fix-v2-1-a6b83a92d9a4@kernel.org
Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
mm/page_alloc.c

index 111b54df8a3cbc5dcd7f811e84c8a680e0528811..b1b1039287e93f557e79ca5679e7c8b3a857c68a 100644 (file)
@@ -7775,6 +7775,11 @@ struct page *alloc_frozen_pages_nolock_noprof(gfp_t gfp_flags, int nid, unsigned
         */
        if (IS_ENABLED(CONFIG_PREEMPT_RT) && (in_nmi() || in_hardirq()))
                return NULL;
+
+       /* On UP, spin_trylock() always succeeds even when it is locked */
+       if (!IS_ENABLED(CONFIG_SMP) && in_nmi())
+               return NULL;
+
        if (!pcp_allowed_order(order))
                return NULL;