]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
mm/memory-failure: fix hugetlb_lock AA deadlock in get_huge_page_for_hwpoison
authorWupeng Ma <mawupeng1@huawei.com>
Fri, 22 May 2026 01:03:05 +0000 (09:03 +0800)
committerAndrew Morton <akpm@linux-foundation.org>
Fri, 29 May 2026 03:50:34 +0000 (20:50 -0700)
Two concurrent madvise(MADV_HWPOISON) calls on the same hugetlb page can
trigger a recursive spinlock self-deadlock (AA deadlock) on hugetlb_lock
when racing with a concurrent unmap:

  thread#0                              thread#1
  --------                              --------
  madvise(folio, MADV_HWPOISON)
    -> poisons the folio successfully
  madvise(folio, MADV_HWPOISON)         unmap(folio)
    try_memory_failure_hugetlb
      get_huge_page_for_hwpoison
        spin_lock_irq(&hugetlb_lock)    <- held
        __get_huge_page_for_hwpoison
          hugetlb_update_hwpoison()
            -> MF_HUGETLB_FOLIO_PRE_POISONED
          goto out:
            folio_put()
              refcount: 1 -> 0
              free_huge_folio()
                spin_lock_irqsave(&hugetlb_lock)
                  -> AA DEADLOCK!

The out: path in __get_huge_page_for_hwpoison() calls folio_put() to drop
the GUP reference while the hugetlb_lock is still held by the hugetlb.c
wrapper get_huge_page_for_hwpoison().  If concurrent unmap has released
the page table mapping reference, folio_put() drops the folio refcount to
zero, triggering free_huge_folio() which attempts to re-acquire the
non-recursive hugetlb_lock.

Fix this by moving hugetlb_lock acquisition from the hugetlb.c wrapper
into get_huge_page_for_hwpoison().  Place spin_unlock_irq() before the
folio_put() at the out: label so the folio is always released outside the
lock.

[akpm@linux-foundation.org: fix race, rename label per Miaohe]
Link: https://sashiko.dev/#/patchset/20260522010305.4099834-1-mawupeng1@huawei.com
Link: https://lore.kernel.org/f39f405e-4b4b-8f79-70fe-a2b5b62114eb@huawei.com
Link: https://lore.kernel.org/20260522010305.4099834-1-mawupeng1@huawei.com
Fixes: 405ce051236c ("mm/hwpoison: fix race between hugetlb free/demotion and memory_failure_hugetlb()")
Signed-off-by: Wupeng Ma <mawupeng1@huawei.com>
Acked-by: Oscar Salvador (SUSE) <osalvador@kernel.org>
Acked-by: Muchun Song <muchun.song@linux.dev>
Reviewed-by: Kefeng Wang <wangkefeng.wang@huawei.com>
Acked-by: Miaohe Lin <linmiaohe@huawei.com>
Cc: David Hildenbrand <david@kernel.org>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Naoya Horiguchi <nao.horiguchi@gmail.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Vlastimil Babka <vbabka@kernel.org>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
include/linux/hugetlb.h
include/linux/mm.h
mm/hugetlb.c
mm/memory-failure.c

index 5957bc25efa856cfd9f1685dc36abab91cc9f144..2abaf99321e90d28c3ab1d995d9aca5c47d1683b 100644 (file)
@@ -153,8 +153,6 @@ long hugetlb_unreserve_pages(struct inode *inode, long start, long end,
                                                long freed);
 bool folio_isolate_hugetlb(struct folio *folio, struct list_head *list);
 int get_hwpoison_hugetlb_folio(struct folio *folio, bool *hugetlb, bool unpoison);
-int get_huge_page_for_hwpoison(unsigned long pfn, int flags,
-                               bool *migratable_cleared);
 void folio_putback_hugetlb(struct folio *folio);
 void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio, int reason);
 void hugetlb_fix_reserve_counts(struct inode *inode);
@@ -421,12 +419,6 @@ static inline int get_hwpoison_hugetlb_folio(struct folio *folio, bool *hugetlb,
        return 0;
 }
 
-static inline int get_huge_page_for_hwpoison(unsigned long pfn, int flags,
-                                       bool *migratable_cleared)
-{
-       return 0;
-}
-
 static inline void folio_putback_hugetlb(struct folio *folio)
 {
 }
index 06bbe9eba636fbadc1fcc23c9d586ed8886a7d1c..fc2acedf0b763d6b3830838f5460b4be942abcf1 100644 (file)
@@ -4975,8 +4975,6 @@ extern int soft_offline_page(unsigned long pfn, int flags);
  */
 extern const struct attribute_group memory_failure_attr_group;
 extern void memory_failure_queue(unsigned long pfn, int flags);
-extern int __get_huge_page_for_hwpoison(unsigned long pfn, int flags,
-                                       bool *migratable_cleared);
 void num_poisoned_pages_inc(unsigned long pfn);
 void num_poisoned_pages_sub(unsigned long pfn, long i);
 #else
@@ -4984,12 +4982,6 @@ static inline void memory_failure_queue(unsigned long pfn, int flags)
 {
 }
 
-static inline int __get_huge_page_for_hwpoison(unsigned long pfn, int flags,
-                                       bool *migratable_cleared)
-{
-       return 0;
-}
-
 static inline void num_poisoned_pages_inc(unsigned long pfn)
 {
 }
index 1b1d4f87a3a4c9f6d08cd4229b48361759d514a1..c921287489de3303303ce47e564d3b62346d01ab 100644 (file)
@@ -7161,17 +7161,6 @@ int get_hwpoison_hugetlb_folio(struct folio *folio, bool *hugetlb, bool unpoison
        return ret;
 }
 
-int get_huge_page_for_hwpoison(unsigned long pfn, int flags,
-                               bool *migratable_cleared)
-{
-       int ret;
-
-       spin_lock_irq(&hugetlb_lock);
-       ret = __get_huge_page_for_hwpoison(pfn, flags, migratable_cleared);
-       spin_unlock_irq(&hugetlb_lock);
-       return ret;
-}
-
 /**
  * folio_putback_hugetlb - unisolate a hugetlb folio
  * @folio: the isolated hugetlb folio
index ee42d43613097f4cb8d58efe0fe13d4f77337b61..d47aef256a32495798690ec8fce6068be4cd864b 100644 (file)
@@ -1966,20 +1966,19 @@ void folio_clear_hugetlb_hwpoison(struct folio *folio)
        folio_free_raw_hwp(folio, true);
 }
 
-/*
- * Called from hugetlb code with hugetlb_lock held.
- */
-int __get_huge_page_for_hwpoison(unsigned long pfn, int flags,
+static int get_huge_page_for_hwpoison(unsigned long pfn, int flags,
                                 bool *migratable_cleared)
 {
        struct page *page = pfn_to_page(pfn);
-       struct folio *folio = page_folio(page);
+       struct folio *folio;
        bool count_increased = false;
        int ret, rc;
 
+       spin_lock_irq(&hugetlb_lock);
+       folio = page_folio(page);
        if (!folio_test_hugetlb(folio)) {
                ret = MF_HUGETLB_NON_HUGEPAGE;
-               goto out;
+               goto out_unlock;
        } else if (flags & MF_COUNT_INCREASED) {
                ret = MF_HUGETLB_IN_USED;
                count_increased = true;
@@ -1995,13 +1994,13 @@ int __get_huge_page_for_hwpoison(unsigned long pfn, int flags,
        } else {
                ret = MF_HUGETLB_RETRY;
                if (!(flags & MF_NO_RETRY))
-                       goto out;
+                       goto out_unlock;
        }
 
        rc = hugetlb_update_hwpoison(folio, page);
        if (rc >= MF_HUGETLB_FOLIO_PRE_POISONED) {
                ret = rc;
-               goto out;
+               goto out_unlock;
        }
 
        /*
@@ -2013,8 +2012,10 @@ int __get_huge_page_for_hwpoison(unsigned long pfn, int flags,
                *migratable_cleared = true;
        }
 
+       spin_unlock_irq(&hugetlb_lock);
        return ret;
-out:
+out_unlock:
+       spin_unlock_irq(&hugetlb_lock);
        if (count_increased)
                folio_put(folio);
        return ret;