[PATCH v3 09/17] crash: Fix TOCTOU race in crash memory range collection
Jinjie Ruan <[email protected]>
| Newsgroups | dev.linux.lists.loongarch,org.infradead.lists.kexec,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kvack.linux-mm,org.ozlabs.lists.linuxppc-dev |
|---|---|
| Message-ID | <[email protected]> |
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: [email protected] Cc: Andrew Morton <[email protected]> Cc: Baoquan He <[email protected]> Cc: Mike Rapoport <[email protected]> Cc: Pasha Tatashin <[email protected]> Cc: Pratyush Yadav <[email protected]> Cc: Dave Young <[email protected]> Cc: AKASHI Takahiro <[email protected]> Cc: Will Deacon <[email protected]> Cc: James Morse <[email protected]> Cc: Palmer Dabbelt <[email protected]> Cc: Youling Tang <[email protected]> Cc: Huacai Chen <[email protected]> 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 <[email protected]> --- arch/x86/kernel/crash.c | 9 ++++++++- include/linux/crash_core.h | 2 +- kernel/crash_core.c | 28 +++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c index e6f23933a6df..8f8c0e592849 100644 --- a/arch/x86/kernel/crash.c +++ b/arch/x86/kernel/crash.c @@ -448,6 +448,7 @@ unsigned int arch_crash_get_elfcorehdr_size(void) void arch_crash_handle_hotplug_event(struct kimage *image, void *arg) { void *elfbuf = NULL, *old_elfcorehdr; + struct crash_mem *cmem = NULL; unsigned long mem, memsz; unsigned long elfsz = 0; @@ -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; } @@ -502,6 +508,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 619af312bd9a..6789ff0af39e 100644 --- a/include/linux/crash_core.h +++ b/include/linux/crash_core.h @@ -62,7 +62,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); -extern int crash_get_memory_ranges(struct crash_mem **mem_ranges); +extern 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 991d1599cf9a..05a2a8be083d 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,7 +318,21 @@ 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; @@ -351,6 +366,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