diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c index b8d0fb0a9daf..78b1f956d8d7 100644 --- a/arch/arm64/kernel/kexec_image.c +++ b/arch/arm64/kernel/kexec_image.c @@ -25,10 +25,10 @@ static int image_probe(const char *kernel_buf, unsigned long kernel_len) (const struct arm64_image_header *)(kernel_buf); if (!h || (kernel_len < sizeof(*h))) - return -EINVAL; + return -ENOEXEC; if (memcmp(&h->magic, ARM64_IMAGE_MAGIC, sizeof(h->magic))) - return -EINVAL; + return -ENOEXEC; return 0; } diff --git a/arch/loongarch/kernel/kexec_efi.c b/arch/loongarch/kernel/kexec_efi.c index 5ee78ebb1546..28a1d0420ba3 100644 --- a/arch/loongarch/kernel/kexec_efi.c +++ b/arch/loongarch/kernel/kexec_efi.c @@ -24,12 +24,12 @@ static int efi_kexec_probe(const char *kernel_buf, unsigned long kernel_len) if (!h || (kernel_len < sizeof(*h))) { kexec_dprintk("No LoongArch image header.\n"); - return -EINVAL; + return -ENOEXEC; } if (!loongarch_header_check_dos_sig(h)) { kexec_dprintk("No LoongArch PE image header.\n"); - return -EINVAL; + return -ENOEXEC; } return 0; diff --git a/arch/riscv/kernel/kexec_elf.c b/arch/riscv/kernel/kexec_elf.c index 3e9a32acb8f2..ca5abbad59e5 100644 --- a/arch/riscv/kernel/kexec_elf.c +++ b/arch/riscv/kernel/kexec_elf.c @@ -91,7 +91,7 @@ static int elf_find_pbase(struct kimage *image, unsigned long kernel_len, kbuf.memsz = ALIGN(kernel_len, PAGE_SIZE); kbuf.cma = NULL; kbuf.top_down = false; - ret = arch_kexec_locate_mem_hole(&kbuf); + ret = kexec_locate_mem_hole(&kbuf); if (!ret) { *old_pbase = lowest_paddr; *new_pbase = kbuf.mem; diff --git a/arch/riscv/kernel/kexec_image.c b/arch/riscv/kernel/kexec_image.c index 51dc89259f16..963a25f5b55b 100644 --- a/arch/riscv/kernel/kexec_image.c +++ b/arch/riscv/kernel/kexec_image.c @@ -20,7 +20,7 @@ static int image_probe(const char *kernel_buf, unsigned long kernel_len) const struct riscv_image_header *h = (const struct riscv_image_header *)kernel_buf; if (!h || kernel_len < sizeof(*h)) - return -EINVAL; + return -ENOEXEC; /* According to Documentation/arch/riscv/boot-image-header.rst, * use "magic2" field to check when version >= 0.2. @@ -28,7 +28,7 @@ static int image_probe(const char *kernel_buf, unsigned long kernel_len) if (h->version >= RISCV_HEADER_VERSION && memcmp(&h->magic2, RISCV_IMAGE_MAGIC2, sizeof(h->magic2))) - return -EINVAL; + return -ENOEXEC; return 0; } diff --git a/include/linux/kexec.h b/include/linux/kexec.h index 0af8ae4fdd08..e5f1cfc11fef 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -259,21 +259,6 @@ int kexec_kernel_verify_pe_sig(const char *kernel, unsigned long kernel_len); extern int kexec_add_buffer(struct kexec_buf *kbuf); int kexec_locate_mem_hole(struct kexec_buf *kbuf); -#ifndef arch_kexec_locate_mem_hole -/** - * arch_kexec_locate_mem_hole - Find free memory to place the segments. - * @kbuf: Parameters for the memory search. - * - * On success, kbuf->mem will have the start address of the memory region found. - * - * Return: 0 on success, negative errno on error. - */ -static inline int arch_kexec_locate_mem_hole(struct kexec_buf *kbuf) -{ - return kexec_locate_mem_hole(kbuf); -} -#endif - #ifndef arch_kexec_apply_relocations_add /* * arch_kexec_apply_relocations_add - apply relocations of type RELA diff --git a/include/linux/mm.h b/include/linux/mm.h index 6882ad400935..691cef038d1e 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5234,6 +5234,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) { @@ -5246,6 +5248,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) diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index dc770b9a6d05..7ee8c9f078f6 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_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 diff --git a/kernel/kexec_elf.c b/kernel/kexec_elf.c index 3a5c25b2adc9..89a444a00693 100644 --- a/kernel/kexec_elf.c +++ b/kernel/kexec_elf.c @@ -172,7 +172,7 @@ static int elf_read_ehdr(const char *buf, size_t len, struct elfhdr *ehdr) default: pr_debug("Unknown ELF class.\n"); - return -EINVAL; + return -ENOEXEC; } return elf_is_ehdr_sane(ehdr, len) ? 0 : -ENOEXEC; @@ -236,7 +236,7 @@ static int elf_read_phdr(const char *buf, size_t len, default: pr_debug("Unknown ELF class.\n"); - return -EINVAL; + return -ENOEXEC; } return elf_is_phdr_sane(phdr, len) ? 0 : -ENOEXEC; diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index 59fb9d71e9d8..a8455481f639 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -68,17 +68,19 @@ int kexec_image_probe_default(struct kimage *image, void *buf, unsigned long buf_len) { const struct kexec_file_ops * const *fops; - int ret = -ENOEXEC; for (fops = &kexec_file_loaders[0]; *fops && (*fops)->probe; ++fops) { - ret = (*fops)->probe(buf, buf_len); - if (!ret) { + int err = (*fops)->probe(buf, buf_len); + + if (!err) { image->fops = *fops; - return ret; + return 0; } + if (err != -ENOEXEC) + return err; } - return ret; + return -ENOEXEC; } static void *kexec_image_load_default(struct kimage *image) @@ -475,6 +477,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 +487,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 +509,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 +532,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 +559,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); @@ -784,7 +804,7 @@ int kexec_add_buffer(struct kexec_buf *kbuf) kbuf->cma = NULL; /* Walk the RAM ranges and allocate a suitable range for the buffer */ - ret = arch_kexec_locate_mem_hole(kbuf); + ret = kexec_locate_mem_hole(kbuf); if (ret) return ret; diff --git a/mm/memory-failure.c b/mm/memory-failure.c index d237f556b3a0..57275d4dd34c 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -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.