[PATCH] mm/page_alloc: fix potential deadlock on zonelist_update_seq seqlock

Tetsuo Handa
2023-04-04T00:37:43Z

syzbot is reporting circular locking dependency which involves
zonelist_update_seq seqlock [1], for this lock is checked by memory
allocation requests which do not need to be retried.

We somehow need to prevent __alloc_pages_slowpath() from checking
this lock. Since Petr Mladek thinks that __build_all_zonelists() can
become a candidate for deferring printk() [2], let's make sure that
current CPU/thread won't reach __alloc_pages_slowpath() while this lock
is in use.

Reported-by: syzbot <syzbot+223c7461c58c58a4cb10@syzkaller.appspotmail.com>
Link: https://syzkaller.appspot.com/bug?extid=223c7461c58c58a4cb10 [1]
Fixes: 3d36424b3b58 ("mm/page_alloc: fix race condition between build_all_zonelists and page allocation")
Link: https://lkml.kernel.org/r/ZCrs+1cDqPWTDFNM@alley [2]
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Petr Mladek <pmladek@suse.com>
---
mm/page_alloc.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)

diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 7136c36c5d01..64fa77b8d24a 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -6632,7 +6632,21 @@ static void __build_all_zonelists(void *data)
int nid;
int __maybe_unused cpu;
pg_data_t *self = data;
+ unsigned long flags;

+ /*
+ * Since __alloc_pages_slowpath() spins if zonelist_update_seq.seqcount
+ * is odd, any memory allocation while zonelist_update_seq.seqcount is
+ * odd have to be avoided.
+ *
+ * Explicitly disable local irqs in order to avoid calling
+ * kmalloc(GFP_ATOMIC) from e.g. timer interrupt handler.
+ * Also, explicitly prevent printk() from synchronously waiting for
+ * port->lock because tty_insert_flip_string_and_push_buffer() might
+ * call kmalloc(GFP_ATOMIC | __GFP_NOWARN) while holding port->lock.
+ */
+ local_irq_save(flags);
+ printk_deferred_enter();
write_seqlock(&zonelist_update_seq);

#ifdef CONFIG_NUMA
@@ -6671,6 +6685,8 @@ static void __build_all_zonelists(void *data)
}

write_sequnlock(&zonelist_update_seq);
+ printk_deferred_exit();
+ local_irq_restore(flags);
}

static noinline void __init
--
2.34.1