Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

stackdepot: fix stack_depot_save_flags() in NMI context

Per documentation, stack_depot_save_flags() was meant to be usable from
NMI context if STACK_DEPOT_FLAG_CAN_ALLOC is unset. However, it still
would try to take the pool_lock in an attempt to save a stack trace in the
current pool (if space is available).

This could result in deadlock if an NMI is handled while pool_lock is
already held. To avoid deadlock, only try to take the lock in NMI context
and give up if unsuccessful.

The documentation is fixed to clearly convey this.

Link: https://lkml.kernel.org/r/Z0CcyfbPqmxJ9uJH@elver.google.com
Link: https://lkml.kernel.org/r/20241122154051.3914732-1-elver@google.com
Fixes: 4434a56ec209 ("stackdepot: make fast paths lock-less again")
Signed-off-by: Marco Elver <elver@google.com>
Reported-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: Alexander Potapenko <glider@google.com>
Cc: Andrey Konovalov <andreyknvl@gmail.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Oscar Salvador <osalvador@suse.de>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Marco Elver and committed by
Andrew Morton
031e04bd 6a7de1bf

+12 -4
+3 -3
include/linux/stackdepot.h
··· 147 147 * If the provided stack trace comes from the interrupt context, only the part 148 148 * up to the interrupt entry is saved. 149 149 * 150 - * Context: Any context, but setting STACK_DEPOT_FLAG_CAN_ALLOC is required if 150 + * Context: Any context, but unsetting STACK_DEPOT_FLAG_CAN_ALLOC is required if 151 151 * alloc_pages() cannot be used from the current context. Currently 152 152 * this is the case for contexts where neither %GFP_ATOMIC nor 153 153 * %GFP_NOWAIT can be used (NMI, raw_spin_lock). ··· 156 156 */ 157 157 depot_stack_handle_t stack_depot_save_flags(unsigned long *entries, 158 158 unsigned int nr_entries, 159 - gfp_t gfp_flags, 159 + gfp_t alloc_flags, 160 160 depot_flags_t depot_flags); 161 161 162 162 /** ··· 175 175 * Return: Handle of the stack trace stored in depot, 0 on failure 176 176 */ 177 177 depot_stack_handle_t stack_depot_save(unsigned long *entries, 178 - unsigned int nr_entries, gfp_t gfp_flags); 178 + unsigned int nr_entries, gfp_t alloc_flags); 179 179 180 180 /** 181 181 * __stack_depot_get_stack_record - Get a pointer to a stack_record struct
+9 -1
lib/stackdepot.c
··· 630 630 prealloc = page_address(page); 631 631 } 632 632 633 - raw_spin_lock_irqsave(&pool_lock, flags); 633 + if (in_nmi()) { 634 + /* We can never allocate in NMI context. */ 635 + WARN_ON_ONCE(can_alloc); 636 + /* Best effort; bail if we fail to take the lock. */ 637 + if (!raw_spin_trylock_irqsave(&pool_lock, flags)) 638 + goto exit; 639 + } else { 640 + raw_spin_lock_irqsave(&pool_lock, flags); 641 + } 634 642 printk_deferred_enter(); 635 643 636 644 /* Try to find again, to avoid concurrently inserting duplicates. */