When show_free_areas() iterates over populated memory zones, it
unconditionally acquires zone->lock via spin_lock_irqsave(). If
show_mem() is invoked from an Oops or panic context while the crashing
CPU or a wedged remote CPU already holds zone->lock, or from an NMI or
hardirq context under CONFIG_PREEMPT_RT where spinlock_t is a sleeping
rt_mutex, acquiring zone->lock can self-deadlock or fault before
post-mortem recovery (such as kdump) can proceed.

Check can_spin_trylock() and use spin_trylock_irqsave() when
!can_spin_trylock(), in_nmi(), oops_in_progress, or panic_in_progress()
is active, and emit the zone header after releasing zone->lock,
logging a contention notice and skipping the locked zone rather than
spinning indefinitely.

Fixes: d999bd9392de ("panic: add options to print system info when panic 
happens")
Signed-off-by: Aaron Tomlin <[email protected]>
---
 mm/show_mem.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/mm/show_mem.c b/mm/show_mem.c
index b938cbcd774a..77098077523a 100644
--- a/mm/show_mem.c
+++ b/mm/show_mem.c
@@ -367,10 +367,17 @@ static void show_free_areas(unsigned int filter, const 
nodemask_t *nodemask,
                        continue;
                if (show_mem_node_skip(filter, zone_to_nid(zone), nodemask))
                        continue;
-               show_node(zone);
-               printk(KERN_CONT "%s: ", zone->name);
 
-               spin_lock_irqsave(&zone->lock, flags);
+               if (!can_spin_trylock() || in_nmi() ||
+                   oops_in_progress || panic_in_progress()) {
+                       if (!can_spin_trylock() ||
+                           !spin_trylock_irqsave(&zone->lock, flags)) {
+                               pr_warn("zone %s lock contended\n", zone->name);
+                               continue;
+                       }
+               } else {
+                       spin_lock_irqsave(&zone->lock, flags);
+               }
                for (order = 0; order < NR_PAGE_ORDERS; order++) {
                        struct free_area *area = &zone->free_area[order];
                        int type;
@@ -385,6 +392,8 @@ static void show_free_areas(unsigned int filter, const 
nodemask_t *nodemask,
                        }
                }
                spin_unlock_irqrestore(&zone->lock, flags);
+               show_node(zone);
+               printk(KERN_CONT "%s: ", zone->name);
                for (order = 0; order < NR_PAGE_ORDERS; order++) {
                        printk(KERN_CONT "%lu*%lukB ",
                               nr[order], K(1UL) << order);
-- 
2.55.0


Reply via email to