[PATCH 6/6] mm/hwpoison-inject: keep the page online while filtering it
From: Gregory Price (Meta)
Date: Tue Oct 27 2026 - 00:23:31 EST
Injecting a memory error with the filter enabled while the memory is
hot-removed can crash. hwpoison_inject() checks the pfn with
pfn_valid(), then runs shake_folio() and the filter on its page, which
can sleep in lru_add_drain_all():
CPU 1 (hwpoison_inject) CPU 2 (ACPI eject)
pfn_valid()
folio = page_folio(pfn_to_page())
offline_pages()
arch_remove_memory()
vmemmap freed
shake_folio(folio)
reads folio->flags
BUG: unable to handle page fault for address: ffd4000006040033
#PF: error_code(0x0000) - not-present page
RIP: 0010:shake_folio+0x2a/0x50
hwpoison_inject+0x232/0x4b0 [hwpoison_inject]
simple_attr_write_xsigned.constprop.0.isra.0+0x179/0x220
debugfs_attr_write+0x63/0x90
Until the filter stopped pinning the page, that reference held off
offline.
Hold get_online_mems() around the filter, check the pfn with
pfn_to_online_page() under it, and drop the hold before
memory_failure(), which takes it again. Offline and ZONE_DEVICE pages
never passed the filter anyway. Export get/put_online_mems() to the
module.
With the injector held at shake_folio() on a free DIMM page across an
eject, base faults in 3 of 3 runs. With this patch the eject waits, and
the injection then fails with -ENXIO, in 3 of 3.
Fixes: fd476720c9ba ("mm,hwpoison-inject: don't pin for hwpoison_filter")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Gregory Price (Meta) <gourry@xxxxxxxxxx>
---
mm/hwpoison-inject.c | 55 +++++++++++++++++++++++++++++---------------
mm/memory_hotplug.c | 2 ++
2 files changed, 39 insertions(+), 18 deletions(-)
diff --git a/mm/hwpoison-inject.c b/mm/hwpoison-inject.c
index a11222572f978..f6cc967b3a468 100644
--- a/mm/hwpoison-inject.c
+++ b/mm/hwpoison-inject.c
@@ -9,6 +9,7 @@
#include <linux/hugetlb.h>
#include <linux/page-flags.h>
#include <linux/memcontrol.h>
+#include <linux/memory_hotplug.h>
#include "internal.h"
static u32 hwpoison_filter_enable;
@@ -99,43 +100,61 @@ static int hwpoison_filter(struct page *p)
static struct dentry *hwpoison_dir;
-static int hwpoison_inject(void *data, u64 val)
+/* Returns true if the filter rejects the page at @pfn */
+static bool hwpoison_inject_filtered(unsigned long pfn)
{
- unsigned long pfn = val;
struct page *p;
struct folio *folio;
- int err;
-
- if (!capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- if (!pfn_valid(pfn))
- return -ENXIO;
- p = pfn_to_page(pfn);
+ /*
+ * Offline and ZONE_DEVICE pages are not LRU, hugetlb or free buddy
+ * pages, so the checks below never let them through.
+ */
+ p = pfn_to_online_page(pfn);
+ if (!p)
+ return true;
folio = page_folio(p);
- if (!hwpoison_filter_enable)
- goto inject;
-
shake_folio(folio);
/*
* This implies unable to support non-LRU pages except free page.
*/
if (!folio_test_lru(folio) && !folio_test_hugetlb(folio) &&
!is_free_buddy_page(p))
- return 0;
+ return true;
/*
* do a racy check to make sure PG_hwpoison will only be set for
* the targeted owner (or on a free page).
* memory_failure() will redo the check reliably inside page lock.
*/
- err = hwpoison_filter(&folio->page);
- if (err)
- return 0;
+ return hwpoison_filter(&folio->page);
+}
+
+static int hwpoison_inject(void *data, u64 val)
+{
+ unsigned long pfn = val;
+ bool filtered;
+ int err;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (!pfn_valid(pfn))
+ return -ENXIO;
+
+ if (hwpoison_filter_enable) {
+ /*
+ * Keep the page online while the filter looks at it.
+ * memory_failure() takes this again, so drop it first.
+ */
+ get_online_mems();
+ filtered = hwpoison_inject_filtered(pfn);
+ put_online_mems();
+ if (filtered)
+ return 0;
+ }
-inject:
pr_info("Injecting memory failure at pfn %#lx\n", pfn);
err = memory_failure(pfn, MF_SW_SIMULATED);
return (err == -EOPNOTSUPP) ? 0 : err;
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index b5d9e367b893d..fe2ed0894d49d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -215,11 +215,13 @@ void get_online_mems(void)
{
percpu_down_read(&mem_hotplug_lock);
}
+EXPORT_SYMBOL_FOR_MODULES(get_online_mems, "hwpoison-inject");
void put_online_mems(void)
{
percpu_up_read(&mem_hotplug_lock);
}
+EXPORT_SYMBOL_FOR_MODULES(put_online_mems, "hwpoison-inject");
bool movable_node_enabled = false;
--
2.53.0-Meta