[PATCH v6 14/14] crash: Fix TOCTOU race in crash memory range collection
flat view
COOLING4d
From: Jinjie Ruan <hidden>
Date: 2026-09-21 09:04:43
Also in:
driver-core, kexec, lkml, loongarch
Subsystem:
kdump, the rest, x86 architecture (32-bit and 64-bit) · Maintainers:
Andrew Morton, Baoquan He, Mike Rapoport, Pasha Tatashin, Pratyush Yadav, Linus Torvalds, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen
The crash kernel ELF core header construction counts system memory
ranges via `arch_get_system_nr_ranges()`, allocates the crash_mem
buffer, and then populates it via `arch_crash_populate_cmem()`.
This sequence has a time-of-check-to-time-of-use (TOCTOU) race with
memory hotplug: a concurrent hotplug event between the count
and populate steps can increase the number of ranges beyond the allocated
capacity, causing an out-of-bounds write. If the event triggers
memblock_double_array(), the memblock array can be freed and reallocated
during iteration, leading to a use-after-free.
Protect the entire range collection with device_hotplug_lock. Since
the hotplug notification path already holds that lock, add a lockless
helper, crash_get_memory_ranges_nolock(), for use there. The regular
crash_get_memory_ranges() acquires the lock and calls the helper.
Cc: stable@vger.kernel.org
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Baoquan He <baoquan.he@linux.dev>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: Pratyush Yadav <pratyush@kernel.org>
Cc: Dave Young <ruirui.yang@linux.dev>
Cc: AKASHI Takahiro <redacted>
Cc: Will Deacon <will@kernel.org>
Cc: James Morse <james.morse@arm.com>
Cc: Palmer Dabbelt <redacted>
Cc: Youling Tang <redacted>
Cc: Huacai Chen <chenhuacai@kernel.org>
Cc: Breno Leitao <leitao@debian.org>
Fixes: 8d5f894a3108 ("x86: kexec_file: lift CRASH_MAX_RANGES limit on crash_mem buffer")
Fixes: 3751e728cef2 ("arm64: kexec_file: add crash dump support")
Fixes: 8acea455fafa ("RISC-V: Support for kexec_file on panic")
Fixes: 1bcca8620a91 ("LoongArch: Add crash dump support for kexec_file")
Link: https://sashiko.dev/#/patchset/20260729031235.2840255-1-ruanjinjie%40huawei.com
Signed-off-by: Jinjie Ruan <redacted>
---
arch/x86/kernel/crash.c | 9 ++++++++-
include/linux/crash_core.h | 2 +-
kernel/crash_core.c | 35 ++++++++++++++++++++++++++++++++++-
3 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c
index f34fa8dba028..7fa139725514 100644
--- a/arch/x86/kernel/crash.c
+++ b/arch/x86/kernel/crash.c@@ -447,6 +447,7 @@ unsigned int arch_crash_get_elfcorehdr_size(void) */ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) { + struct crash_mem *cmem = NULL; unsigned long mem, memsz; unsigned long elfsz = 0; void *elfbuf = NULL;
@@ -461,11 +462,16 @@ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) image->hp_action == KEXEC_CRASH_HP_REMOVE_CPU) return; + if (crash_get_memory_ranges_nolock(&cmem)) { + pr_err("Failed to get crash mem range\n"); + goto out; + } + /* * Create the new elfcorehdr reflecting the changes to CPU and/or * memory resources. */ - if (crash_prepare_headers(IS_ENABLED(CONFIG_X86_64), &elfbuf, &elfsz, NULL)) { + if (crash_prepare_elf64_headers(cmem, IS_ENABLED(CONFIG_X86_64), &elfbuf, &elfsz)) { pr_err("unable to create new elfcorehdr"); goto out; }
@@ -501,6 +507,7 @@ void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) pr_debug("updated elfcorehdr\n"); out: + kvfree(cmem); vfree(elfbuf); } #endif
diff --git a/include/linux/crash_core.h b/include/linux/crash_core.h
index a740757dff35..1296a9b29974 100644
--- a/include/linux/crash_core.h
+++ b/include/linux/crash_core.h@@ -63,7 +63,7 @@ extern int crash_prepare_elf64_headers(struct crash_mem *mem, int need_kernel_ma extern int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz, unsigned long *nr_mem_ranges); extern int crash_exclude_core_ranges(struct crash_mem **cmem); -int crash_get_memory_ranges(struct crash_mem **mem_ranges); +int crash_get_memory_ranges_nolock(struct crash_mem **mem_ranges); struct kimage; struct kexec_segment;
diff --git a/kernel/crash_core.c b/kernel/crash_core.c
index b638ad0e5ba2..43b21de1d64a 100644
--- a/kernel/crash_core.c
+++ b/kernel/crash_core.c@@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/buildid.h> +#include <linux/device.h> #include <linux/init.h> #include <linux/utsname.h> #include <linux/vmalloc.h>
@@ -317,12 +318,33 @@ int crash_exclude_core_ranges(struct crash_mem **cmem) return 0; } -int crash_get_memory_ranges(struct crash_mem **mem_ranges) +/** + * crash_get_memory_ranges_nolock - Collect crash kernel memory ranges + * @mem_ranges: Output parameter for the allocated crash_mem structure + * + * Gathers the system memory ranges to be included in the crash kernel's + * ELF core header, excluding the crashkernel reserved region and other + * architecture-specific areas. + * + * Context: Caller must hold device_hotplug_lock. + * + * Return: 0 on success, in which case *@mem_ranges points to a newly + * allocated struct crash_mem that the caller must free with kvfree(). + * Returns a negative error code on failure. + */ +int crash_get_memory_ranges_nolock(struct crash_mem **mem_ranges) { unsigned int max_nr_ranges; struct crash_mem *cmem; int ret; + /* + * Callers must serialize against memory hotplug by holding + * device_hotplug_lock, otherwise the memblock iteration below can + * race with memblock_double_array() and read freed memory. + */ + device_hotplug_lock_assert_held(); + max_nr_ranges = arch_get_system_nr_ranges(); if (!max_nr_ranges) return -ENOMEM;
@@ -351,6 +373,17 @@ int crash_get_memory_ranges(struct crash_mem **mem_ranges) return ret; } +static int crash_get_memory_ranges(struct crash_mem **mem_ranges) +{ + int ret; + + lock_device_hotplug(); + ret = crash_get_memory_ranges_nolock(mem_ranges); + unlock_device_hotplug(); + + return ret; +} + int crash_prepare_headers(int need_kernel_map, void **addr, unsigned long *sz, unsigned long *nr_mem_ranges) {
--
2.34.1