[PATCH v2 2/2] mm/hugetlb: clear the hugetlb flag before leaking a hwpoisoned folio
From: Mani Tofigh via B4 Relay
Date: Tue Nov 03 2026 - 16:01:23 EST
From: Mani Tofigh <mt4022@xxxxxxxxxxxx>
If hugetlb_update_hwpoison() fails to allocate a raw_hwp_page, the folio
is marked raw_hwp_unreliable and __update_and_free_hugetlb_folio() leaks
it intentionally. With hugetlb_free_vmemmap=on, it's leaked before the
hugetlb flag is cleared, since commit 32c877191e02 ("hugetlb: do not
clear hugetlb dtor until allocating vmemmap") doesn't remove the flag
until the vmemmap is restored. The leaked folio is then a hugetlb folio
with refcount 0 and on no list (the same state as a hugetlb folio that
is being freed), but nothing is freeing it, and
dissolve_free_hugetlb_folio() keeps retrying on it. Offlining its
memory block then never completes, even on SIGKILL, and other tasks
block on the memory hotplug lock:
[ 3.271255] Memory failure: 0x20007: recovery action for free huge page: Failed
[ 41.729695] INFO: task hugeoffline:77 blocked for more than 20 seconds.
...
[ 41.733014] get_online_mems+0xc1/0xd0
[ 41.733026] soft_offline_page+0xfd/0x9b0
alloc_and_dissolve_hugetlb_folio() loops on it the same way. The
refcount is 0 since commit bd225530a4c7 ("mm/hugetlb_vmemmap: fix race
with speculative PFN walkers"). Before that these paths failed with
-EBUSY.
Check raw_hwp_unreliable after the hugetlb flag is cleared so that the
folio is leaked as a non-hugetlb folio, as without vmemmap
optimization. unpoison_memory() keeps refusing to unpoison it. With
the fix, offlining retries as it does for any page it can't isolate,
and stops on a signal.
Fixes: 32c877191e02 ("hugetlb: do not clear hugetlb dtor until allocating vmemmap")
Fixes: bd225530a4c7 ("mm/hugetlb_vmemmap: fix race with speculative PFN walkers")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mani Tofigh <mt4022@xxxxxxxxxxxx>
---
This relies on Kaitao's "mm/hugetlb: preserve persistent flags when
returning folios to the pool" [1] (673b553e2bba in mm-new). The check
now comes after hugetlb_vmemmap_restore_folio(), so on a restore failure
add_hugetlb_folio() must keep raw_hwp_unreliable. It would be good to
take [1] to stable along with this one.
This changes the same lines as patch 07/15 of Matthew's "Use
generic_file_read_iter() in hugetlbfs" series [2]. On top of that
series, the check goes right after the folio_clear_hugetlb() call.
[1] https://lore.kernel.org/all/20261102024910.31486-3-kaitao.cheng@xxxxxxxxx/
[2] https://lore.kernel.org/all/20260805210557.1118966-8-willy@xxxxxxxxxxxxx/
Reproducer: https://github.com/manitofigh/files/blob/main/linux-repros/hugeoffline.c
---
mm/hugetlb.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 779b1de33c78..cdeb8923083f 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1480,13 +1480,6 @@ static void __update_and_free_hugetlb_folio(struct hstate *h,
return;
}
- /*
- * If we don't know which subpages are hwpoisoned, we can't free
- * the hugepage, so it's leaked intentionally.
- */
- if (folio_test_hugetlb_raw_hwp_unreliable(folio))
- return;
-
/*
* If folio is not vmemmap optimized (!clear_flag), then the folio
* is no longer identified as a hugetlb page. hugetlb_vmemmap_restore_folio
@@ -1514,6 +1507,14 @@ static void __update_and_free_hugetlb_folio(struct hstate *h,
spin_unlock_irq(&hugetlb_lock);
}
+ /*
+ * If we don't know which subpages are hwpoisoned, we can't free
+ * the hugepage, so it's leaked intentionally, but only once it is
+ * no longer a hugetlb folio.
+ */
+ if (folio_test_hugetlb_raw_hwp_unreliable(folio))
+ return;
+
/*
* Move PageHWPoison flag from head page to the raw error pages,
* which makes any healthy subpages reusable.
--
2.53.0