Define CPU_PRESERVED_TEXT and CPU_PRESERVED_DATA linker macros to place code and data required for CPU preservation into dedicated sections (.text.cpu_preserved and .data.cpu_preserved).
These sections are preserved across live update transitions and mapped executable/read-write as needed. Signed-off-by: Pasha Tatashin <[email protected]> --- include/asm-generic/vmlinux.lds.h | 29 +++++++++++++++++ include/linux/cpu_preserve.h | 47 +++++++++++++++++++++++++++ scripts/mod/modpost.c | 32 +++++++++++++++--- tools/objtool/check.c | 35 +++++++++++++++++++- tools/objtool/include/objtool/check.h | 1 + tools/objtool/include/objtool/elf.h | 2 +- 6 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 include/linux/cpu_preserve.h diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index b2988aa12f66..0dbaac582bcc 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -655,6 +655,34 @@ *(.static_call.text) \ __static_call_text_end = .; +/* + * Page-aligned text and data sections for preserved CPUs. + * This code and data are KHO preserved when CPUs are preserved across + * live update. + */ +#ifdef CONFIG_LIVEUPDATE_CPU +#define CPU_PRESERVED_TEXT \ + . = ALIGN(PAGE_SIZE); \ + __cpu_preserved_text_start = .; \ + *(.text.cpu_preserved .text.cpu_preserved.*) \ + *(.cpu_preserved.text .cpu_preserved.text.*) \ + . = ALIGN(PAGE_SIZE); \ + __cpu_preserved_text_end = .; + +#define CPU_PRESERVED_DATA \ + . = ALIGN(PAGE_SIZE); \ + __cpu_preserved_data_start = .; \ + *(.data.cpu_preserved .data.cpu_preserved.*) \ + *(.cpu_preserved.data .cpu_preserved.data.*) \ + *(.rodata.cpu_preserved .rodata.cpu_preserved.*) \ + *(.bss..data.cpu_preserved .bss..data.cpu_preserved.*) \ + . = ALIGN(PAGE_SIZE); \ + __cpu_preserved_data_end = .; +#else +#define CPU_PRESERVED_TEXT +#define CPU_PRESERVED_DATA +#endif + /* Section used for early init (in .S files) */ #define HEAD_TEXT KEEP(*(.head.text)) @@ -1155,6 +1183,7 @@ INIT_TASK_DATA(inittask) \ NOSAVE_DATA \ PAGE_ALIGNED_DATA(pagealigned) \ + CPU_PRESERVED_DATA \ CACHE_HOT_DATA(cacheline) \ CACHELINE_ALIGNED_DATA(cacheline) \ READ_MOSTLY_DATA(cacheline) \ diff --git a/include/linux/cpu_preserve.h b/include/linux/cpu_preserve.h new file mode 100644 index 000000000000..f653838c383d --- /dev/null +++ b/include/linux/cpu_preserve.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2026, Google LLC. + * Pasha Tatashin <[email protected]> + * + * Preserved CPU across Live Update + */ +#ifndef _LINUX_CPU_PRESERVE_H +#define _LINUX_CPU_PRESERVE_H + +#include <linux/compiler.h> + +#ifdef CONFIG_LIVEUPDATE_CPU + +/* + * __cpu_preserved_text: Code executed by preserved physical CPUs during live + * update kexec handover in orphan mode. + * + * All code in this section must run without stack protector checks because + * per-CPU canary state may be invalid during handover and __stack_chk_fail() + * resides in regular .text, which gets overwritten during kexec before the + * incoming kernel boots. + * + * Architecture-specific requirements (such as disabling external retpolines + * and return thunks on x86) are supplied via ARCH_CPU_PRESERVED_TEXT. + */ +#ifndef ARCH_CPU_PRESERVED_TEXT +#define ARCH_CPU_PRESERVED_TEXT +#endif + +#define __cpu_preserved_text \ + __section(".text.cpu_preserved") \ + __no_stack_protector \ + ARCH_CPU_PRESERVED_TEXT +#define __cpu_preserved_data __section(".data.cpu_preserved") + +extern char __cpu_preserved_text_start[], __cpu_preserved_text_end[]; +extern char __cpu_preserved_data_start[], __cpu_preserved_data_end[]; + +#else /* !CONFIG_LIVEUPDATE_CPU */ + +#define __cpu_preserved_text +#define __cpu_preserved_data + +#endif /* CONFIG_LIVEUPDATE_CPU */ + +#endif /* _LINUX_CPU_PRESERVE_H */ diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index 75374c64b8cc..51631cd2f6c4 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -810,13 +810,27 @@ static void check_section(struct module *mod, struct elf_info *elf, #define ALL_INIT_SECTIONS ".init.*" #define ALL_EXIT_SECTIONS ".exit.*" +#define ALL_CPU_PRESERVED_TEXT_SECTIONS \ + ".text.cpu_preserved", ".text.cpu_preserved.*", \ + ".cpu_preserved.text", ".cpu_preserved.text.*" + +#define ALL_CPU_PRESERVED_DATA_SECTIONS \ + ".data.cpu_preserved", ".data.cpu_preserved.*", \ + ".cpu_preserved.data", ".cpu_preserved.data.*", \ + ".rodata.cpu_preserved", ".rodata.cpu_preserved.*", \ + ".bss..data.cpu_preserved", ".bss..data.cpu_preserved.*" + +#define ALL_CPU_PRESERVED_SECTIONS \ + ALL_CPU_PRESERVED_TEXT_SECTIONS, ALL_CPU_PRESERVED_DATA_SECTIONS + #define DATA_SECTIONS ".data", ".data.rel" #define TEXT_SECTIONS ".text", ".text.*", ".sched.text", \ ".kprobes.text", ".cpuidle.text", ".noinstr.text", \ ".ltext", ".ltext.*" #define OTHER_TEXT_SECTIONS ".ref.text", ".head.text", ".spinlock.text", \ ".fixup", ".entry.text", ".exception.text", \ - ".coldtext", ".softirqentry.text", ".irqentry.text" + ".coldtext", ".softirqentry.text", ".irqentry.text", \ + ALL_CPU_PRESERVED_TEXT_SECTIONS #define ALL_TEXT_SECTIONS ".init.text", ".exit.text", \ TEXT_SECTIONS, OTHER_TEXT_SECTIONS @@ -827,6 +841,7 @@ enum mismatch { ANY_INIT_TO_ANY_EXIT, ANY_EXIT_TO_ANY_INIT, EXTABLE_TO_NON_TEXT, + CPU_PRESERVED_TO_NON_PRESERVED, }; /** @@ -843,13 +858,19 @@ enum mismatch { * @mismatch: Type of mismatch. */ struct sectioncheck { - const char *fromsec[20]; - const char *bad_tosec[20]; - const char *good_tosec[20]; + const char *fromsec[32]; + const char *bad_tosec[32]; + const char *good_tosec[32]; enum mismatch mismatch; }; static const struct sectioncheck sectioncheck[] = { +/* Do not reference non-preserved code/data from cpu_preserved sections */ +{ + .fromsec = { ALL_CPU_PRESERVED_SECTIONS, NULL }, + .good_tosec = { ALL_CPU_PRESERVED_SECTIONS, NULL }, + .mismatch = CPU_PRESERVED_TO_NON_PRESERVED, +}, /* Do not reference init/exit code/data from * normal code and data */ @@ -960,6 +981,9 @@ static const struct sectioncheck *section_mismatch( static int secref_whitelist(const char *fromsec, const char *fromsym, const char *tosec, const char *tosym) { + if (match(fromsec, PATTERNS(ALL_CPU_PRESERVED_SECTIONS))) + return 1; + /* Check for pattern 1 */ if (match(tosec, PATTERNS(ALL_INIT_DATA_SECTIONS)) && match(fromsec, PATTERNS(DATA_SECTIONS)) && diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 464f6c9d9ff0..77389fbfd9ea 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -329,6 +329,8 @@ static void init_insn_state(struct objtool_file *file, struct insn_state *state, if (opts.noinstr && sec) state->noinstr = sec->noinstr; + if (sec) + state->cpu_preserved = sec->cpu_preserved; } static struct cfi_state *cfi_alloc(void) @@ -419,6 +421,14 @@ static int decode_instructions(struct objtool_file *file) u8 prev_len = 0; u8 idx = 0; + if (!strncmp(sec->name, ".text.cpu_preserved", 19) || + !strncmp(sec->name, ".cpu_preserved.text", 19) || + !strncmp(sec->name, ".data.cpu_preserved", 19) || + !strncmp(sec->name, ".cpu_preserved.data", 19) || + !strncmp(sec->name, ".rodata.cpu_preserved", 21) || + !strncmp(sec->name, ".bss..data.cpu_preserved", 24)) + sec->cpu_preserved = true; + if (!is_text_sec(sec)) continue; @@ -3511,6 +3521,17 @@ static int validate_call(struct objtool_file *file, struct instruction *insn, struct insn_state *state) { + if (state->cpu_preserved) { + struct symbol *dest = insn_call_dest(insn); + + if (dest && (dest->sec->idx != SHN_UNDEF || opts.link) && + !dest->sec->cpu_preserved) { + WARN_INSN(insn, "call to %s() leaves .text.cpu_preserved section", + call_dest_name(insn)); + return 1; + } + } + if (state->noinstr && state->instr <= 0 && !noinstr_call_dest(file, insn, insn_call_dest(insn))) { WARN_INSN(insn, "call to %s() leaves .noinstr.text section", call_dest_name(insn)); @@ -4164,7 +4185,13 @@ static int validate_retpoline(struct objtool_file *file) if (insn->retpoline_safe) continue; - if (insn->sec->init) + /* + * Preserved CPU text (.text.cpu_preserved) executes across + * kexec when the outgoing kernel's retpoline/rethunk targets + * are no longer mapped. + */ + if (insn->sec->init || + !strcmp(insn->sec->name, ".text.cpu_preserved")) continue; if (insn->type == INSN_RETURN) { @@ -4440,6 +4467,12 @@ static int validate_noinstr_sections(struct objtool_file *file) warnings += validate_unwind_hints(file, sec); } + sec = find_section_by_name(file->elf, ".text.cpu_preserved"); + if (sec) { + warnings += validate_section(file, sec); + warnings += validate_unwind_hints(file, sec); + } + return warnings; } diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 063f5985fecd..18318f2faf11 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -16,6 +16,7 @@ struct insn_state { bool uaccess; bool df; bool noinstr; + bool cpu_preserved; s8 instr; }; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index a82517a76a0f..bac9e5f4921f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -58,7 +58,7 @@ struct section { Elf_Data *data; const char *name; int idx; - bool _changed, text, rodata, noinstr, init, truncate; + bool _changed, text, rodata, noinstr, init, cpu_preserved, truncate; struct reloc *relocs; unsigned long nr_alloc_relocs; struct section *twin; -- 2.55.0.1082.g2b9226bbc0-goog

