Merge patch series "kexec: keep the next kernel off hardware-poisoned pages"

Breno Leitao <leitao@debian.org> says:

Memory failures are common enough on large fleets that kexec regularly
lands the next kernel on a frame the memory failure subsystem has already
marked bad. Patch 2 teaches the segment placement to avoid those frames.

Patch 1 is a prerequisite. locate_mem_hole_top_down() walks candidates
downwards without ever checking that the subtraction stays above zero, so
the walk can wrap and report success with a destination outside of RAM.
Patch 2 adds one more downward step to that loop, so the bug is fixed
first and both patches rely on the same bail-out.
This commit is contained in:
Mike Rapoport (Microsoft) 2026-08-31 14:20:07 +03:00
commit c7add4fee3
4 changed files with 83 additions and 1 deletions

View File

@ -5254,6 +5254,8 @@ 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);
phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size);
phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size);
#else
static inline void memory_failure_queue(unsigned long pfn, int flags)
{
@ -5266,6 +5268,18 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
{
}
static inline phys_addr_t range_first_hwpoison(phys_addr_t start,
unsigned long size)
{
return PHYS_ADDR_MAX;
}
static inline phys_addr_t range_last_hwpoison(phys_addr_t start,
unsigned long size)
{
return PHYS_ADDR_MAX;
}
#endif
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)

View File

@ -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_first_hwpoison(image->segment[i].mem,
image->segment[i].memsz) != PHYS_ADDR_MAX)
return -EHWPOISON;
}
/*
* The destination addresses are searched from system RAM rather than
* being allocated from the buddy allocator, so they are not guaranteed

View File

@ -475,6 +475,7 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
{
struct kimage *image = kbuf->image;
unsigned long temp_start, temp_end;
phys_addr_t poison;
temp_end = min(end, kbuf->buf_max);
temp_start = temp_end - kbuf->memsz + 1;
@ -484,7 +485,9 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
/* align down start */
temp_start = ALIGN_DOWN(temp_start, kbuf->buf_align);
if (temp_start < start || temp_start < kbuf->buf_min)
/* A candidate above the range means the walk wrapped around */
if (temp_start < start || temp_start < kbuf->buf_min ||
temp_start > end)
return 0;
temp_end = temp_start + kbuf->memsz - 1;
@ -504,6 +507,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
continue;
}
poison = range_first_hwpoison(temp_start, kbuf->memsz);
if (poison != PHYS_ADDR_MAX) {
/* we hit a poisoned page */
temp_start = poison - kbuf->memsz;
continue;
}
/* We found a suitable memory range */
break;
} while (1);
@ -520,6 +530,7 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
{
struct kimage *image = kbuf->image;
unsigned long temp_start, temp_end;
phys_addr_t poison;
temp_start = max(start, kbuf->buf_min);
@ -546,6 +557,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
continue;
}
poison = range_last_hwpoison(temp_start, kbuf->memsz);
if (poison != PHYS_ADDR_MAX) {
/* we hit a poisoned page */
temp_start = poison + PAGE_SIZE;
continue;
}
/* We found a suitable memory range */
break;
} while (1);

View File

@ -96,6 +96,46 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
memblk_nr_poison_sub(pfn, i);
}
/*
* Return the first or the last hardware-poisoned online page in [start,
* start + size), or PHYS_ADDR_MAX if the range is clean.
*/
static phys_addr_t range_hwpoison(phys_addr_t start, unsigned long size,
bool first)
{
phys_addr_t poison = PHYS_ADDR_MAX;
unsigned long pfn, end_pfn;
if (!size || !atomic_long_read(&num_poisoned_pages))
return poison;
end_pfn = PHYS_PFN(start + size - 1);
for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
struct page *page = pfn_to_online_page(pfn);
if (page && is_page_hwpoison(page)) {
if (first)
return PFN_PHYS(pfn);
poison = PFN_PHYS(pfn);
}
cond_resched();
}
return poison;
}
phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size)
{
return range_hwpoison(start, size, true);
}
phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size)
{
return range_hwpoison(start, size, false);
}
/**
* MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
* @_name: name of the file in the per NUMA sysfs directory.