* [PATCH] kexec: keep the next kernel off hardware-poisoned pages
@ 2026-07-28 16:22 Breno Leitao
2026-07-29 9:33 ` Miaohe Lin
2026-07-29 11:57 ` Pratyush Yadav
0 siblings, 2 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-28 16:22 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Miaohe Lin, Naoya Horiguchi
Cc: linux-mm, linux-kernel, kexec, rmikey, riel, kernel-team,
Kiryl Shutsemau, Breno Leitao
Memory failures (such as unrecoverable ECCs errors) are getting more and
more common. The kernel knows how to handle it while running, marking it
as poisoned (and SIGBUS user tasks).
Poisoned memory is removed from the buddy allocator, but, not from
other places. A current problem is that kexec will load new kernel
on top of a bad/poisoned memory, which is undesirable.
If the next kernel's image, initrd or purgatory lands on poisoned frame,
the relocation copy writes to the bad memory and the machine checks
during the kexec.
Skip hardware-poisoned frames when placing segments: check them in the
kexec_file hole finder so it lays the next kernel down on good memory,
and reject a poisoned destination in sanity_check_segment_list() for
the kexec_load path, which cannot relocate.
Suggested-by: Kiryl Shutsemau <kas@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/mm.h | 6 ++++++
kernel/kexec_core.c | 10 ++++++++++
kernel/kexec_file.c | 14 ++++++++++++++
mm/memory-failure.c | 18 ++++++++++++++++++
4 files changed, 48 insertions(+)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 7fabe6c66b4b7..a89108bcc3f90 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -5192,6 +5192,7 @@ extern const struct attribute_group memory_failure_attr_group;
extern void memory_failure_queue(unsigned long pfn, int flags);
void num_poisoned_pages_inc(unsigned long pfn);
void num_poisoned_pages_sub(unsigned long pfn, long i);
+bool range_contains_hwpoison(phys_addr_t start, unsigned long size);
#else
static inline void memory_failure_queue(unsigned long pfn, int flags)
{
@@ -5204,6 +5205,11 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
{
}
+
+static inline bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
+{
+ return false;
+}
#endif
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index dc770b9a6d053..18793f925835f 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -212,6 +212,16 @@ int sanity_check_segment_list(struct kimage *image)
}
#endif
+ /*
+ * Reject destinations that land on hardware-poisoned memory: the
+ * relocation copy would machine-check on the bad frame.
+ */
+ for (i = 0; i < nr_segments; i++) {
+ if (range_contains_hwpoison(image->segment[i].mem,
+ image->segment[i].memsz))
+ return -EADDRNOTAVAIL;
+ }
+
/*
* The destination addresses are searched from system RAM rather than
* being allocated from the buddy allocator, so they are not guaranteed
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 59fb9d71e9d86..cd77350f12f85 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -504,6 +504,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
continue;
}
+ /* Avoid placing the next kernel on hardware-poisoned memory */
+ if (range_contains_hwpoison(temp_start,
+ temp_end - temp_start + 1)) {
+ temp_start = temp_start - PAGE_SIZE;
+ continue;
+ }
+
/* We found a suitable memory range */
break;
} while (1);
@@ -546,6 +553,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
continue;
}
+ /* Avoid placing the next kernel on hardware-poisoned memory */
+ if (range_contains_hwpoison(temp_start,
+ temp_end - temp_start + 1)) {
+ temp_start = temp_start + PAGE_SIZE;
+ continue;
+ }
+
/* We found a suitable memory range */
break;
} while (1);
diff --git a/mm/memory-failure.c b/mm/memory-failure.c
index a8b03e2920ba8..032bb23db7566 100644
--- a/mm/memory-failure.c
+++ b/mm/memory-failure.c
@@ -96,6 +96,24 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
memblk_nr_poison_sub(pfn, i);
}
+/*
+ * Check if any page in [start, start + size) has hardware-poisoned segments
+ */
+bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
+{
+ unsigned long pfn, end_pfn;
+
+ if (!size || !atomic_long_read(&num_poisoned_pages))
+ return false;
+
+ end_pfn = PHYS_PFN(start + size - 1);
+ for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
+ if (pfn_valid(pfn) && PageHWPoison(pfn_to_page(pfn)))
+ return true;
+ }
+ return false;
+}
+
/**
* MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
* @_name: name of the file in the per NUMA sysfs directory.
---
base-commit: c5e32e86ca02b003f86e095d379b38148999293d
change-id: 20260727-kexec_posioned-72bb0a4143a0
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply related [flat|nested] 4+ messages in thread* Re: [PATCH] kexec: keep the next kernel off hardware-poisoned pages
2026-07-28 16:22 [PATCH] kexec: keep the next kernel off hardware-poisoned pages Breno Leitao
@ 2026-07-29 9:33 ` Miaohe Lin
2026-07-29 10:17 ` Breno Leitao
2026-07-29 11:57 ` Pratyush Yadav
1 sibling, 1 reply; 4+ messages in thread
From: Miaohe Lin @ 2026-07-29 9:33 UTC (permalink / raw)
To: Breno Leitao
Cc: linux-mm, linux-kernel, kexec, rmikey, riel, kernel-team,
Kiryl Shutsemau, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Naoya Horiguchi
On 2026/7/29 0:22, Breno Leitao wrote:
> Memory failures (such as unrecoverable ECCs errors) are getting more and
> more common. The kernel knows how to handle it while running, marking it
> as poisoned (and SIGBUS user tasks).
>
> Poisoned memory is removed from the buddy allocator, but, not from
> other places. A current problem is that kexec will load new kernel
> on top of a bad/poisoned memory, which is undesirable.
>
> If the next kernel's image, initrd or purgatory lands on poisoned frame,
> the relocation copy writes to the bad memory and the machine checks
> during the kexec.
>
> Skip hardware-poisoned frames when placing segments: check them in the
> kexec_file hole finder so it lays the next kernel down on good memory,
> and reject a poisoned destination in sanity_check_segment_list() for
> the kexec_load path, which cannot relocate.
>
> Suggested-by: Kiryl Shutsemau <kas@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> include/linux/mm.h | 6 ++++++
> kernel/kexec_core.c | 10 ++++++++++
> kernel/kexec_file.c | 14 ++++++++++++++
> mm/memory-failure.c | 18 ++++++++++++++++++
> 4 files changed, 48 insertions(+)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 7fabe6c66b4b7..a89108bcc3f90 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5192,6 +5192,7 @@ extern const struct attribute_group memory_failure_attr_group;
> extern void memory_failure_queue(unsigned long pfn, int flags);
> void num_poisoned_pages_inc(unsigned long pfn);
> void num_poisoned_pages_sub(unsigned long pfn, long i);
> +bool range_contains_hwpoison(phys_addr_t start, unsigned long size);
> #else
> static inline void memory_failure_queue(unsigned long pfn, int flags)
> {
> @@ -5204,6 +5205,11 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
> static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
> {
> }
> +
> +static inline bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
> +{
> + return false;
> +}
> #endif
>
> #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
> diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
> index dc770b9a6d053..18793f925835f 100644
> --- a/kernel/kexec_core.c
> +++ b/kernel/kexec_core.c
> @@ -212,6 +212,16 @@ int sanity_check_segment_list(struct kimage *image)
> }
> #endif
>
> + /*
> + * Reject destinations that land on hardware-poisoned memory: the
> + * relocation copy would machine-check on the bad frame.
> + */
> + for (i = 0; i < nr_segments; i++) {
> + if (range_contains_hwpoison(image->segment[i].mem,
> + image->segment[i].memsz))
> + return -EADDRNOTAVAIL;
> + }
> +
> /*
> * The destination addresses are searched from system RAM rather than
> * being allocated from the buddy allocator, so they are not guaranteed
> diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
> index 59fb9d71e9d86..cd77350f12f85 100644
> --- a/kernel/kexec_file.c
> +++ b/kernel/kexec_file.c
> @@ -504,6 +504,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
> continue;
> }
>
> + /* Avoid placing the next kernel on hardware-poisoned memory */
> + if (range_contains_hwpoison(temp_start,
> + temp_end - temp_start + 1)) {
> + temp_start = temp_start - PAGE_SIZE;
> + continue;
> + }
> +
> /* We found a suitable memory range */
> break;
> } while (1);
> @@ -546,6 +553,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
> continue;
> }
>
> + /* Avoid placing the next kernel on hardware-poisoned memory */
> + if (range_contains_hwpoison(temp_start,
> + temp_end - temp_start + 1)) {
> + temp_start = temp_start + PAGE_SIZE;
> + continue;
> + }
> +
> /* We found a suitable memory range */
> break;
> } while (1);
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index a8b03e2920ba8..032bb23db7566 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -96,6 +96,24 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
> memblk_nr_poison_sub(pfn, i);
> }
>
> +/*
> + * Check if any page in [start, start + size) has hardware-poisoned segments
> + */
> +bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
> +{
> + unsigned long pfn, end_pfn;
> +
> + if (!size || !atomic_long_read(&num_poisoned_pages))
> + return false;
> +
> + end_pfn = PHYS_PFN(start + size - 1);
> + for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
> + if (pfn_valid(pfn) && PageHWPoison(pfn_to_page(pfn)))
Should we use pfn_to_online_page here? I doubt we might meet the same problem
in [1] if pfn_to_page is used here.
[1]: https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?id=d613f53c83ec47089c4e25859d5e8e0359f6f8da
Thanks.
.
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] kexec: keep the next kernel off hardware-poisoned pages
2026-07-29 9:33 ` Miaohe Lin
@ 2026-07-29 10:17 ` Breno Leitao
0 siblings, 0 replies; 4+ messages in thread
From: Breno Leitao @ 2026-07-29 10:17 UTC (permalink / raw)
To: Miaohe Lin
Cc: linux-mm, linux-kernel, kexec, rmikey, riel, kernel-team,
Kiryl Shutsemau, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Naoya Horiguchi
On Wed, Jul 29, 2026 at 05:33:17PM +0800, Miaohe Lin wrote:
> > + if (pfn_valid(pfn) && PageHWPoison(pfn_to_page(pfn)))
>
> Should we use pfn_to_online_page here? I doubt we might meet the same problem
> in [1] if pfn_to_page is used here.
Good catch, thanks -- yes. An offlined but not removed block is still
pfn_valid() while its struct page is uninitialized, so PageHWPoison()
on it hits VM_BUG_ON_PAGE(PagePoisoned(page)), the same failure as
commit d613f53c83ec ("mm/memory-failure: fix
VM_BUG_ON_PAGE(PagePoisoned(page)) when unpoison memory") fixes.
I'll switch to pfn_to_online_page() in v2:
for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
struct page *page = pfn_to_online_page(pfn);
if (page && PageHWPoison(page))
return true;
}
Thanks for the review.
--breno
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] kexec: keep the next kernel off hardware-poisoned pages
2026-07-28 16:22 [PATCH] kexec: keep the next kernel off hardware-poisoned pages Breno Leitao
2026-07-29 9:33 ` Miaohe Lin
@ 2026-07-29 11:57 ` Pratyush Yadav
1 sibling, 0 replies; 4+ messages in thread
From: Pratyush Yadav @ 2026-07-29 11:57 UTC (permalink / raw)
To: Breno Leitao
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Baoquan He, Pasha Tatashin,
Pratyush Yadav, Miaohe Lin, Naoya Horiguchi, linux-mm,
linux-kernel, kexec, rmikey, riel, kernel-team, Kiryl Shutsemau
On Tue, Jul 28 2026, Breno Leitao wrote:
> Memory failures (such as unrecoverable ECCs errors) are getting more and
> more common. The kernel knows how to handle it while running, marking it
> as poisoned (and SIGBUS user tasks).
>
> Poisoned memory is removed from the buddy allocator, but, not from
> other places. A current problem is that kexec will load new kernel
> on top of a bad/poisoned memory, which is undesirable.
>
> If the next kernel's image, initrd or purgatory lands on poisoned frame,
> the relocation copy writes to the bad memory and the machine checks
> during the kexec.
>
> Skip hardware-poisoned frames when placing segments: check them in the
> kexec_file hole finder so it lays the next kernel down on good memory,
> and reject a poisoned destination in sanity_check_segment_list() for
> the kexec_load path, which cannot relocate.
kexec_locate_mem_hole() uses kexec_alloc_contig() to try to find a
contiguous range from CMA. If found, it uses that and skips relocation.
Do you know if CMA tracks poisoned pages and skips allocating them?
sanity_check_segment_list() will catch these anyway and error out, but
would be nice if CMA can track poisoned pages in the first place.
Anyway, that is out of scope for this patch, but something you might
want to look into.
>
> Suggested-by: Kiryl Shutsemau <kas@kernel.org>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> include/linux/mm.h | 6 ++++++
> kernel/kexec_core.c | 10 ++++++++++
> kernel/kexec_file.c | 14 ++++++++++++++
> mm/memory-failure.c | 18 ++++++++++++++++++
> 4 files changed, 48 insertions(+)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 7fabe6c66b4b7..a89108bcc3f90 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -5192,6 +5192,7 @@ extern const struct attribute_group memory_failure_attr_group;
> extern void memory_failure_queue(unsigned long pfn, int flags);
> void num_poisoned_pages_inc(unsigned long pfn);
> void num_poisoned_pages_sub(unsigned long pfn, long i);
> +bool range_contains_hwpoison(phys_addr_t start, unsigned long size);
> #else
> static inline void memory_failure_queue(unsigned long pfn, int flags)
> {
> @@ -5204,6 +5205,11 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
> static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
> {
> }
> +
> +static inline bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
> +{
> + return false;
> +}
> #endif
>
> #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
> diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
> index dc770b9a6d053..18793f925835f 100644
> --- a/kernel/kexec_core.c
> +++ b/kernel/kexec_core.c
> @@ -212,6 +212,16 @@ int sanity_check_segment_list(struct kimage *image)
> }
> #endif
>
> + /*
> + * Reject destinations that land on hardware-poisoned memory: the
> + * relocation copy would machine-check on the bad frame.
> + */
> + for (i = 0; i < nr_segments; i++) {
> + if (range_contains_hwpoison(image->segment[i].mem,
> + image->segment[i].memsz))
> + return -EADDRNOTAVAIL;
> + }
> +
> /*
> * The destination addresses are searched from system RAM rather than
> * being allocated from the buddy allocator, so they are not guaranteed
> diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
> index 59fb9d71e9d86..cd77350f12f85 100644
> --- a/kernel/kexec_file.c
> +++ b/kernel/kexec_file.c
> @@ -504,6 +504,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
> continue;
> }
>
> + /* Avoid placing the next kernel on hardware-poisoned memory */
> + if (range_contains_hwpoison(temp_start,
> + temp_end - temp_start + 1)) {
> + temp_start = temp_start - PAGE_SIZE;
Sashiko complains about this
https://sashiko.dev/#/patchset/20260728-kexec_posioned-v1-1-160c81d180fe@debian.org:
If we find a hardware-poisoned page within a large segment, does this loop in
locate_mem_hole_top_down() cause a CPU soft lockup?
Since temp_start is shifted by only PAGE_SIZE, the next iteration will call
range_contains_hwpoison() again. As seen in mm/memory-failure.c, that
function does a linear scan from the new start address:
for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
if (pfn_valid(pfn) && PageHWPoison(pfn_to_page(pfn)))
return true;
}
This means we could rescan almost the entire segment over and over until it
finally moves past the poisoned page. For a very large segment like a 512MB
initrd, this would iterate billions of times without yielding.
Could range_contains_hwpoison() return the address of the poisoned page so
the hole finder can skip past it efficiently?
Which does seem to make sense. Same problem below.
> + continue;
> + }
> +
> /* We found a suitable memory range */
> break;
> } while (1);
> @@ -546,6 +553,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
> continue;
> }
>
> + /* Avoid placing the next kernel on hardware-poisoned memory */
> + if (range_contains_hwpoison(temp_start,
> + temp_end - temp_start + 1)) {
> + temp_start = temp_start + PAGE_SIZE;
> + continue;
> + }
> +
> /* We found a suitable memory range */
> break;
> } while (1);
> diff --git a/mm/memory-failure.c b/mm/memory-failure.c
> index a8b03e2920ba8..032bb23db7566 100644
> --- a/mm/memory-failure.c
> +++ b/mm/memory-failure.c
> @@ -96,6 +96,24 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
> memblk_nr_poison_sub(pfn, i);
> }
>
> +/*
> + * Check if any page in [start, start + size) has hardware-poisoned segments
> + */
> +bool range_contains_hwpoison(phys_addr_t start, unsigned long size)
> +{
> + unsigned long pfn, end_pfn;
> +
> + if (!size || !atomic_long_read(&num_poisoned_pages))
> + return false;
> +
> + end_pfn = PHYS_PFN(start + size - 1);
> + for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
> + if (pfn_valid(pfn) && PageHWPoison(pfn_to_page(pfn)))
> + return true;
> + }
> + return false;
> +}
> +
> /**
> * MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
> * @_name: name of the file in the per NUMA sysfs directory.
>
> ---
> base-commit: c5e32e86ca02b003f86e095d379b38148999293d
> change-id: 20260727-kexec_posioned-72bb0a4143a0
>
> Best regards,
> --
>
> Breno Leitao <leitao@debian.org>
>
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-29 11:57 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 16:22 [PATCH] kexec: keep the next kernel off hardware-poisoned pages Breno Leitao
2026-07-29 9:33 ` Miaohe Lin
2026-07-29 10:17 ` Breno Leitao
2026-07-29 11:57 ` Pratyush Yadav
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.