| From 9b869776c72be7456d34378e32d96c4663c5c4b6 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Tue, 20 Jan 2026 20:40:04 +0800 |
| Subject: kexec: derive purgatory entry from symbol |
| |
| From: Li Chen <me@linux.beauty> |
| |
| [ Upstream commit 480e1d5c64bb14441f79f2eb9421d5e26f91ea3d ] |
| |
| kexec_load_purgatory() derives image->start by locating e_entry inside an |
| SHF_EXECINSTR section. If the purgatory object contains multiple |
| executable sections with overlapping sh_addr, the entrypoint check can |
| match more than once and trigger a WARN. |
| |
| Derive the entry section from the purgatory_start symbol when present and |
| compute image->start from its final placement. Keep the existing e_entry |
| fallback for purgatories that do not expose the symbol. |
| |
| WARNING: kernel/kexec_file.c:1009 at kexec_load_purgatory+0x395/0x3c0, CPU#10: kexec/1784 |
| Call Trace: |
| <TASK> |
| bzImage64_load+0x133/0xa00 |
| __do_sys_kexec_file_load+0x2b3/0x5c0 |
| do_syscall_64+0x81/0x610 |
| entry_SYSCALL_64_after_hwframe+0x76/0x7e |
| |
| [me@linux.beauty: move helper to avoid forward declaration, per Baoquan] |
| Link: https://lkml.kernel.org/r/20260128043511.316860-1-me@linux.beauty |
| Link: https://lkml.kernel.org/r/20260120124005.148381-1-me@linux.beauty |
| Fixes: 8652d44f466a ("kexec: support purgatories with .text.hot sections") |
| Signed-off-by: Li Chen <me@linux.beauty> |
| Acked-by: Baoquan He <bhe@redhat.com> |
| Cc: Alexander Graf <graf@amazon.com> |
| Cc: Eric Biggers <ebiggers@kernel.org> |
| Cc: Li Chen <me@linux.beauty> |
| Cc: Philipp Rudo <prudo@redhat.com> |
| Cc: Ricardo Ribalda Delgado <ribalda@chromium.org> |
| Cc: Ross Zwisler <zwisler@google.com> |
| Cc: Sourabh Jain <sourabhjain@linux.ibm.com> |
| Cc: Steven Rostedt <rostedt@goodmis.org> |
| Cc: <stable@vger.kernel.org> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| kernel/kexec_file.c | 131 +++++++++++++++++++++++++------------------- |
| 1 file changed, 74 insertions(+), 57 deletions(-) |
| |
| diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c |
| index 3eedb8c226ad8..f852528bdc246 100644 |
| --- a/kernel/kexec_file.c |
| +++ b/kernel/kexec_file.c |
| @@ -821,6 +821,60 @@ static int kexec_calculate_store_digests(struct kimage *image) |
| } |
| |
| #ifdef CONFIG_ARCH_SUPPORTS_KEXEC_PURGATORY |
| +/* |
| + * kexec_purgatory_find_symbol - find a symbol in the purgatory |
| + * @pi: Purgatory to search in. |
| + * @name: Name of the symbol. |
| + * |
| + * Return: pointer to symbol in read-only symtab on success, NULL on error. |
| + */ |
| +static const Elf_Sym *kexec_purgatory_find_symbol(struct purgatory_info *pi, |
| + const char *name) |
| +{ |
| + const Elf_Shdr *sechdrs; |
| + const Elf_Ehdr *ehdr; |
| + const Elf_Sym *syms; |
| + const char *strtab; |
| + int i, k; |
| + |
| + if (!pi->ehdr) |
| + return NULL; |
| + |
| + ehdr = pi->ehdr; |
| + sechdrs = (void *)ehdr + ehdr->e_shoff; |
| + |
| + for (i = 0; i < ehdr->e_shnum; i++) { |
| + if (sechdrs[i].sh_type != SHT_SYMTAB) |
| + continue; |
| + |
| + if (sechdrs[i].sh_link >= ehdr->e_shnum) |
| + /* Invalid strtab section number */ |
| + continue; |
| + strtab = (void *)ehdr + sechdrs[sechdrs[i].sh_link].sh_offset; |
| + syms = (void *)ehdr + sechdrs[i].sh_offset; |
| + |
| + /* Go through symbols for a match */ |
| + for (k = 0; k < sechdrs[i].sh_size/sizeof(Elf_Sym); k++) { |
| + if (ELF_ST_BIND(syms[k].st_info) != STB_GLOBAL) |
| + continue; |
| + |
| + if (strcmp(strtab + syms[k].st_name, name) != 0) |
| + continue; |
| + |
| + if (syms[k].st_shndx == SHN_UNDEF || |
| + syms[k].st_shndx >= ehdr->e_shnum) { |
| + pr_debug("Symbol: %s has bad section index %d.\n", |
| + name, syms[k].st_shndx); |
| + return NULL; |
| + } |
| + |
| + /* Found the symbol we are looking for */ |
| + return &syms[k]; |
| + } |
| + } |
| + |
| + return NULL; |
| +} |
| /* |
| * kexec_purgatory_setup_kbuf - prepare buffer to load purgatory. |
| * @pi: Purgatory to be loaded. |
| @@ -899,6 +953,10 @@ static int kexec_purgatory_setup_sechdrs(struct purgatory_info *pi, |
| unsigned long offset; |
| size_t sechdrs_size; |
| Elf_Shdr *sechdrs; |
| + const Elf_Sym *entry_sym; |
| + u16 entry_shndx = 0; |
| + unsigned long entry_off = 0; |
| + bool start_fixed = false; |
| int i; |
| |
| /* |
| @@ -916,6 +974,12 @@ static int kexec_purgatory_setup_sechdrs(struct purgatory_info *pi, |
| bss_addr = kbuf->mem + kbuf->bufsz; |
| kbuf->image->start = pi->ehdr->e_entry; |
| |
| + entry_sym = kexec_purgatory_find_symbol(pi, "purgatory_start"); |
| + if (entry_sym) { |
| + entry_shndx = entry_sym->st_shndx; |
| + entry_off = entry_sym->st_value; |
| + } |
| + |
| for (i = 0; i < pi->ehdr->e_shnum; i++) { |
| unsigned long align; |
| void *src, *dst; |
| @@ -933,6 +997,13 @@ static int kexec_purgatory_setup_sechdrs(struct purgatory_info *pi, |
| |
| offset = ALIGN(offset, align); |
| |
| + if (!start_fixed && entry_sym && i == entry_shndx && |
| + (sechdrs[i].sh_flags & SHF_EXECINSTR) && |
| + entry_off < sechdrs[i].sh_size) { |
| + kbuf->image->start = kbuf->mem + offset + entry_off; |
| + start_fixed = true; |
| + } |
| + |
| /* |
| * Check if the segment contains the entry point, if so, |
| * calculate the value of image->start based on it. |
| @@ -943,13 +1014,14 @@ static int kexec_purgatory_setup_sechdrs(struct purgatory_info *pi, |
| * is not set to the initial value, and warn the user so they |
| * have a chance to fix their purgatory's linker script. |
| */ |
| - if (sechdrs[i].sh_flags & SHF_EXECINSTR && |
| + if (!start_fixed && sechdrs[i].sh_flags & SHF_EXECINSTR && |
| pi->ehdr->e_entry >= sechdrs[i].sh_addr && |
| pi->ehdr->e_entry < (sechdrs[i].sh_addr |
| + sechdrs[i].sh_size) && |
| - !WARN_ON(kbuf->image->start != pi->ehdr->e_entry)) { |
| + kbuf->image->start == pi->ehdr->e_entry) { |
| kbuf->image->start -= sechdrs[i].sh_addr; |
| kbuf->image->start += kbuf->mem + offset; |
| + start_fixed = true; |
| } |
| |
| src = (void *)pi->ehdr + sechdrs[i].sh_offset; |
| @@ -1067,61 +1139,6 @@ int kexec_load_purgatory(struct kimage *image, struct kexec_buf *kbuf) |
| return ret; |
| } |
| |
| -/* |
| - * kexec_purgatory_find_symbol - find a symbol in the purgatory |
| - * @pi: Purgatory to search in. |
| - * @name: Name of the symbol. |
| - * |
| - * Return: pointer to symbol in read-only symtab on success, NULL on error. |
| - */ |
| -static const Elf_Sym *kexec_purgatory_find_symbol(struct purgatory_info *pi, |
| - const char *name) |
| -{ |
| - const Elf_Shdr *sechdrs; |
| - const Elf_Ehdr *ehdr; |
| - const Elf_Sym *syms; |
| - const char *strtab; |
| - int i, k; |
| - |
| - if (!pi->ehdr) |
| - return NULL; |
| - |
| - ehdr = pi->ehdr; |
| - sechdrs = (void *)ehdr + ehdr->e_shoff; |
| - |
| - for (i = 0; i < ehdr->e_shnum; i++) { |
| - if (sechdrs[i].sh_type != SHT_SYMTAB) |
| - continue; |
| - |
| - if (sechdrs[i].sh_link >= ehdr->e_shnum) |
| - /* Invalid strtab section number */ |
| - continue; |
| - strtab = (void *)ehdr + sechdrs[sechdrs[i].sh_link].sh_offset; |
| - syms = (void *)ehdr + sechdrs[i].sh_offset; |
| - |
| - /* Go through symbols for a match */ |
| - for (k = 0; k < sechdrs[i].sh_size/sizeof(Elf_Sym); k++) { |
| - if (ELF_ST_BIND(syms[k].st_info) != STB_GLOBAL) |
| - continue; |
| - |
| - if (strcmp(strtab + syms[k].st_name, name) != 0) |
| - continue; |
| - |
| - if (syms[k].st_shndx == SHN_UNDEF || |
| - syms[k].st_shndx >= ehdr->e_shnum) { |
| - pr_debug("Symbol: %s has bad section index %d.\n", |
| - name, syms[k].st_shndx); |
| - return NULL; |
| - } |
| - |
| - /* Found the symbol we are looking for */ |
| - return &syms[k]; |
| - } |
| - } |
| - |
| - return NULL; |
| -} |
| - |
| void *kexec_purgatory_get_symbol_addr(struct kimage *image, const char *name) |
| { |
| struct purgatory_info *pi = &image->purgatory_info; |
| -- |
| 2.51.0 |
| |