[PATCH 1/1] x86/crash: reserve elfcorehdr for CONFIG_NR_CPUS, not CONFIG_NR_CPUS_DEFAULT

From: Ionut Nechita (Wind River)

Date: Wed Aug 12 2026 - 13:07:16 EST


From: Ionut Nechita <ionut.nechita@xxxxxxxxxxxxx>

kexec_file_load(2) fails with -EINVAL when loading a crash kernel on a
machine whose number of possible CPUs exceeds CONFIG_NR_CPUS_DEFAULT,
even though the classic kexec_load(2) path succeeds on the same machine.

With CONFIG_CRASH_HOTPLUG=y the elfcorehdr segment is over-allocated so
it can be updated in place on CPU/memory hotplug. crash_load_segments()
and arch_crash_get_elfcorehdr_size() compute that reservation from
CONFIG_NR_CPUS_DEFAULT:

if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
pnum = 2 + CONFIG_NR_CPUS_DEFAULT + CONFIG_CRASH_MAX_MEMORY_RANGES;
else
pnum += 2 + CONFIG_NR_CPUS_DEFAULT;

However the actual header produced by crash_prepare_elf64_headers()
contains one PT_NOTE per *possible* CPU, i.e. num_possible_cpus(), which
is bounded by CONFIG_NR_CPUS, not by CONFIG_NR_CPUS_DEFAULT. On configs
that raise CONFIG_NR_CPUS above the arch default while leaving
CONFIG_NR_CPUS_DEFAULT untouched (e.g. CONFIG_NR_CPUS=256,
CONFIG_NR_CPUS_DEFAULT=64 on x86_64 without MAXSMP), a system with more
than ~CONFIG_NR_CPUS_DEFAULT possible CPUs builds a header whose bufsz
exceeds the reserved, page-aligned memsz. sanity_check_segment_list()
then rejects the image:

if (image->segment[i].bufsz > image->segment[i].memsz)
return -EINVAL;

kexec_load(2) is unaffected because user space builds the elfcorehdr
without the hotplug over-allocation.

Observed on a single-socket Xeon 6776P (144 possible CPUs) running a
PREEMPT_RT kernel with:

# CONFIG_MAXSMP is not set
CONFIG_NR_CPUS_RANGE_BEGIN=2
CONFIG_NR_CPUS_RANGE_END=512
CONFIG_NR_CPUS_DEFAULT=64
CONFIG_NR_CPUS=256

kexec -p -s fails with "kexec_file_load failed: Invalid argument".
Reducing the possible CPU count below the page-rounding threshold
(e.g. 72 via firmware) makes it succeed, confirming the reservation is
the limiting factor.

Reserve the elfcorehdr for CONFIG_NR_CPUS, the compile-time upper bound
of num_possible_cpus(), so the reservation always covers the header that
is actually generated.

Fixes: a72bbec70da2 ("crash: hotplug support for kexec_load()")
Signed-off-by: Ionut Nechita <ionut.nechita@xxxxxxxxxxxxx>
---
arch/x86/kernel/crash.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index e681ec9cf1dc8..e6f23933a6df2 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c
@@ -369,9 +369,9 @@ int crash_load_segments(struct kimage *image)
* maximum CPUs and maximum memory ranges.
*/
if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
- pnum = 2 + CONFIG_NR_CPUS_DEFAULT + CONFIG_CRASH_MAX_MEMORY_RANGES;
+ pnum = 2 + CONFIG_NR_CPUS + CONFIG_CRASH_MAX_MEMORY_RANGES;
else
- pnum += 2 + CONFIG_NR_CPUS_DEFAULT;
+ pnum += 2 + CONFIG_NR_CPUS;

if (pnum < (unsigned long)PN_XNUM) {
kbuf.memsz = pnum * sizeof(Elf64_Phdr);
@@ -430,7 +430,7 @@ unsigned int arch_crash_get_elfcorehdr_size(void)
unsigned int sz;

/* kernel_map, VMCOREINFO and maximum CPUs */
- sz = 2 + CONFIG_NR_CPUS_DEFAULT;
+ sz = 2 + CONFIG_NR_CPUS;
if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
sz += CONFIG_CRASH_MAX_MEMORY_RANGES;
sz *= sizeof(Elf64_Phdr);
--
2.43.0