|
@@ -22,50 +22,123 @@
|
|
|
#include <linux/ima.h>
|
|
|
#include <crypto/hash.h>
|
|
|
#include <crypto/sha.h>
|
|
|
+#include <linux/elf.h>
|
|
|
+#include <linux/elfcore.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/kexec.h>
|
|
|
+#include <linux/slab.h>
|
|
|
#include <linux/syscalls.h>
|
|
|
#include <linux/vmalloc.h>
|
|
|
#include "kexec_internal.h"
|
|
|
|
|
|
static int kexec_calculate_store_digests(struct kimage *image);
|
|
|
|
|
|
+/*
|
|
|
+ * Currently this is the only default function that is exported as some
|
|
|
+ * architectures need it to do additional handlings.
|
|
|
+ * In the future, other default functions may be exported too if required.
|
|
|
+ */
|
|
|
+int kexec_image_probe_default(struct kimage *image, void *buf,
|
|
|
+ unsigned long buf_len)
|
|
|
+{
|
|
|
+ const struct kexec_file_ops * const *fops;
|
|
|
+ int ret = -ENOEXEC;
|
|
|
+
|
|
|
+ for (fops = &kexec_file_loaders[0]; *fops && (*fops)->probe; ++fops) {
|
|
|
+ ret = (*fops)->probe(buf, buf_len);
|
|
|
+ if (!ret) {
|
|
|
+ image->fops = *fops;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
/* Architectures can provide this probe function */
|
|
|
int __weak arch_kexec_kernel_image_probe(struct kimage *image, void *buf,
|
|
|
unsigned long buf_len)
|
|
|
{
|
|
|
- return -ENOEXEC;
|
|
|
+ return kexec_image_probe_default(image, buf, buf_len);
|
|
|
+}
|
|
|
+
|
|
|
+static void *kexec_image_load_default(struct kimage *image)
|
|
|
+{
|
|
|
+ if (!image->fops || !image->fops->load)
|
|
|
+ return ERR_PTR(-ENOEXEC);
|
|
|
+
|
|
|
+ return image->fops->load(image, image->kernel_buf,
|
|
|
+ image->kernel_buf_len, image->initrd_buf,
|
|
|
+ image->initrd_buf_len, image->cmdline_buf,
|
|
|
+ image->cmdline_buf_len);
|
|
|
}
|
|
|
|
|
|
void * __weak arch_kexec_kernel_image_load(struct kimage *image)
|
|
|
{
|
|
|
- return ERR_PTR(-ENOEXEC);
|
|
|
+ return kexec_image_load_default(image);
|
|
|
+}
|
|
|
+
|
|
|
+static int kexec_image_post_load_cleanup_default(struct kimage *image)
|
|
|
+{
|
|
|
+ if (!image->fops || !image->fops->cleanup)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ return image->fops->cleanup(image->image_loader_data);
|
|
|
}
|
|
|
|
|
|
int __weak arch_kimage_file_post_load_cleanup(struct kimage *image)
|
|
|
{
|
|
|
- return -EINVAL;
|
|
|
+ return kexec_image_post_load_cleanup_default(image);
|
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_KEXEC_VERIFY_SIG
|
|
|
+static int kexec_image_verify_sig_default(struct kimage *image, void *buf,
|
|
|
+ unsigned long buf_len)
|
|
|
+{
|
|
|
+ if (!image->fops || !image->fops->verify_sig) {
|
|
|
+ pr_debug("kernel loader does not support signature verification.\n");
|
|
|
+ return -EKEYREJECTED;
|
|
|
+ }
|
|
|
+
|
|
|
+ return image->fops->verify_sig(buf, buf_len);
|
|
|
+}
|
|
|
+
|
|
|
int __weak arch_kexec_kernel_verify_sig(struct kimage *image, void *buf,
|
|
|
unsigned long buf_len)
|
|
|
{
|
|
|
- return -EKEYREJECTED;
|
|
|
+ return kexec_image_verify_sig_default(image, buf, buf_len);
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
-/* Apply relocations of type RELA */
|
|
|
+/*
|
|
|
+ * arch_kexec_apply_relocations_add - apply relocations of type RELA
|
|
|
+ * @pi: Purgatory to be relocated.
|
|
|
+ * @section: Section relocations applying to.
|
|
|
+ * @relsec: Section containing RELAs.
|
|
|
+ * @symtab: Corresponding symtab.
|
|
|
+ *
|
|
|
+ * Return: 0 on success, negative errno on error.
|
|
|
+ */
|
|
|
int __weak
|
|
|
-arch_kexec_apply_relocations_add(const Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
|
|
|
- unsigned int relsec)
|
|
|
+arch_kexec_apply_relocations_add(struct purgatory_info *pi, Elf_Shdr *section,
|
|
|
+ const Elf_Shdr *relsec, const Elf_Shdr *symtab)
|
|
|
{
|
|
|
pr_err("RELA relocation unsupported.\n");
|
|
|
return -ENOEXEC;
|
|
|
}
|
|
|
|
|
|
-/* Apply relocations of type REL */
|
|
|
+/*
|
|
|
+ * arch_kexec_apply_relocations - apply relocations of type REL
|
|
|
+ * @pi: Purgatory to be relocated.
|
|
|
+ * @section: Section relocations applying to.
|
|
|
+ * @relsec: Section containing RELs.
|
|
|
+ * @symtab: Corresponding symtab.
|
|
|
+ *
|
|
|
+ * Return: 0 on success, negative errno on error.
|
|
|
+ */
|
|
|
int __weak
|
|
|
-arch_kexec_apply_relocations(const Elf_Ehdr *ehdr, Elf_Shdr *sechdrs,
|
|
|
- unsigned int relsec)
|
|
|
+arch_kexec_apply_relocations(struct purgatory_info *pi, Elf_Shdr *section,
|
|
|
+ const Elf_Shdr *relsec, const Elf_Shdr *symtab)
|
|
|
{
|
|
|
pr_err("REL relocation unsupported.\n");
|
|
|
return -ENOEXEC;
|
|
@@ -532,6 +605,9 @@ static int kexec_calculate_store_digests(struct kimage *image)
|
|
|
struct kexec_sha_region *sha_regions;
|
|
|
struct purgatory_info *pi = &image->purgatory_info;
|
|
|
|
|
|
+ if (!IS_ENABLED(CONFIG_ARCH_HAS_KEXEC_PURGATORY))
|
|
|
+ return 0;
|
|
|
+
|
|
|
zero_buf = __va(page_to_pfn(ZERO_PAGE(0)) << PAGE_SHIFT);
|
|
|
zero_buf_sz = PAGE_SIZE;
|
|
|
|
|
@@ -633,87 +709,29 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-/* Actually load purgatory. Lot of code taken from kexec-tools */
|
|
|
-static int __kexec_load_purgatory(struct kimage *image, unsigned long min,
|
|
|
- unsigned long max, int top_down)
|
|
|
+#ifdef CONFIG_ARCH_HAS_KEXEC_PURGATORY
|
|
|
+/*
|
|
|
+ * kexec_purgatory_setup_kbuf - prepare buffer to load purgatory.
|
|
|
+ * @pi: Purgatory to be loaded.
|
|
|
+ * @kbuf: Buffer to setup.
|
|
|
+ *
|
|
|
+ * Allocates the memory needed for the buffer. Caller is responsible to free
|
|
|
+ * the memory after use.
|
|
|
+ *
|
|
|
+ * Return: 0 on success, negative errno on error.
|
|
|
+ */
|
|
|
+static int kexec_purgatory_setup_kbuf(struct purgatory_info *pi,
|
|
|
+ struct kexec_buf *kbuf)
|
|
|
{
|
|
|
- struct purgatory_info *pi = &image->purgatory_info;
|
|
|
- unsigned long align, bss_align, bss_sz, bss_pad;
|
|
|
- unsigned long entry, load_addr, curr_load_addr, bss_addr, offset;
|
|
|
- unsigned char *buf_addr, *src;
|
|
|
- int i, ret = 0, entry_sidx = -1;
|
|
|
- const Elf_Shdr *sechdrs_c;
|
|
|
- Elf_Shdr *sechdrs = NULL;
|
|
|
- struct kexec_buf kbuf = { .image = image, .bufsz = 0, .buf_align = 1,
|
|
|
- .buf_min = min, .buf_max = max,
|
|
|
- .top_down = top_down };
|
|
|
-
|
|
|
- /*
|
|
|
- * sechdrs_c points to section headers in purgatory and are read
|
|
|
- * only. No modifications allowed.
|
|
|
- */
|
|
|
- sechdrs_c = (void *)pi->ehdr + pi->ehdr->e_shoff;
|
|
|
-
|
|
|
- /*
|
|
|
- * We can not modify sechdrs_c[] and its fields. It is read only.
|
|
|
- * Copy it over to a local copy where one can store some temporary
|
|
|
- * data and free it at the end. We need to modify ->sh_addr and
|
|
|
- * ->sh_offset fields to keep track of permanent and temporary
|
|
|
- * locations of sections.
|
|
|
- */
|
|
|
- sechdrs = vzalloc(pi->ehdr->e_shnum * sizeof(Elf_Shdr));
|
|
|
- if (!sechdrs)
|
|
|
- return -ENOMEM;
|
|
|
-
|
|
|
- memcpy(sechdrs, sechdrs_c, pi->ehdr->e_shnum * sizeof(Elf_Shdr));
|
|
|
-
|
|
|
- /*
|
|
|
- * We seem to have multiple copies of sections. First copy is which
|
|
|
- * is embedded in kernel in read only section. Some of these sections
|
|
|
- * will be copied to a temporary buffer and relocated. And these
|
|
|
- * sections will finally be copied to their final destination at
|
|
|
- * segment load time.
|
|
|
- *
|
|
|
- * Use ->sh_offset to reflect section address in memory. It will
|
|
|
- * point to original read only copy if section is not allocatable.
|
|
|
- * Otherwise it will point to temporary copy which will be relocated.
|
|
|
- *
|
|
|
- * Use ->sh_addr to contain final address of the section where it
|
|
|
- * will go during execution time.
|
|
|
- */
|
|
|
- for (i = 0; i < pi->ehdr->e_shnum; i++) {
|
|
|
- if (sechdrs[i].sh_type == SHT_NOBITS)
|
|
|
- continue;
|
|
|
-
|
|
|
- sechdrs[i].sh_offset = (unsigned long)pi->ehdr +
|
|
|
- sechdrs[i].sh_offset;
|
|
|
- }
|
|
|
-
|
|
|
- /*
|
|
|
- * Identify entry point section and make entry relative to section
|
|
|
- * start.
|
|
|
- */
|
|
|
- entry = pi->ehdr->e_entry;
|
|
|
- for (i = 0; i < pi->ehdr->e_shnum; i++) {
|
|
|
- if (!(sechdrs[i].sh_flags & SHF_ALLOC))
|
|
|
- continue;
|
|
|
-
|
|
|
- if (!(sechdrs[i].sh_flags & SHF_EXECINSTR))
|
|
|
- continue;
|
|
|
-
|
|
|
- /* Make entry section relative */
|
|
|
- if (sechdrs[i].sh_addr <= pi->ehdr->e_entry &&
|
|
|
- ((sechdrs[i].sh_addr + sechdrs[i].sh_size) >
|
|
|
- pi->ehdr->e_entry)) {
|
|
|
- entry_sidx = i;
|
|
|
- entry -= sechdrs[i].sh_addr;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ const Elf_Shdr *sechdrs;
|
|
|
+ unsigned long bss_align;
|
|
|
+ unsigned long bss_sz;
|
|
|
+ unsigned long align;
|
|
|
+ int i, ret;
|
|
|
|
|
|
- /* Determine how much memory is needed to load relocatable object. */
|
|
|
- bss_align = 1;
|
|
|
- bss_sz = 0;
|
|
|
+ sechdrs = (void *)pi->ehdr + pi->ehdr->e_shoff;
|
|
|
+ kbuf->buf_align = bss_align = 1;
|
|
|
+ kbuf->bufsz = bss_sz = 0;
|
|
|
|
|
|
for (i = 0; i < pi->ehdr->e_shnum; i++) {
|
|
|
if (!(sechdrs[i].sh_flags & SHF_ALLOC))
|
|
@@ -721,111 +739,124 @@ static int __kexec_load_purgatory(struct kimage *image, unsigned long min,
|
|
|
|
|
|
align = sechdrs[i].sh_addralign;
|
|
|
if (sechdrs[i].sh_type != SHT_NOBITS) {
|
|
|
- if (kbuf.buf_align < align)
|
|
|
- kbuf.buf_align = align;
|
|
|
- kbuf.bufsz = ALIGN(kbuf.bufsz, align);
|
|
|
- kbuf.bufsz += sechdrs[i].sh_size;
|
|
|
+ if (kbuf->buf_align < align)
|
|
|
+ kbuf->buf_align = align;
|
|
|
+ kbuf->bufsz = ALIGN(kbuf->bufsz, align);
|
|
|
+ kbuf->bufsz += sechdrs[i].sh_size;
|
|
|
} else {
|
|
|
- /* bss section */
|
|
|
if (bss_align < align)
|
|
|
bss_align = align;
|
|
|
bss_sz = ALIGN(bss_sz, align);
|
|
|
bss_sz += sechdrs[i].sh_size;
|
|
|
}
|
|
|
}
|
|
|
+ kbuf->bufsz = ALIGN(kbuf->bufsz, bss_align);
|
|
|
+ kbuf->memsz = kbuf->bufsz + bss_sz;
|
|
|
+ if (kbuf->buf_align < bss_align)
|
|
|
+ kbuf->buf_align = bss_align;
|
|
|
|
|
|
- /* Determine the bss padding required to align bss properly */
|
|
|
- bss_pad = 0;
|
|
|
- if (kbuf.bufsz & (bss_align - 1))
|
|
|
- bss_pad = bss_align - (kbuf.bufsz & (bss_align - 1));
|
|
|
-
|
|
|
- kbuf.memsz = kbuf.bufsz + bss_pad + bss_sz;
|
|
|
+ kbuf->buffer = vzalloc(kbuf->bufsz);
|
|
|
+ if (!kbuf->buffer)
|
|
|
+ return -ENOMEM;
|
|
|
+ pi->purgatory_buf = kbuf->buffer;
|
|
|
|
|
|
- /* Allocate buffer for purgatory */
|
|
|
- kbuf.buffer = vzalloc(kbuf.bufsz);
|
|
|
- if (!kbuf.buffer) {
|
|
|
- ret = -ENOMEM;
|
|
|
+ ret = kexec_add_buffer(kbuf);
|
|
|
+ if (ret)
|
|
|
goto out;
|
|
|
- }
|
|
|
|
|
|
- if (kbuf.buf_align < bss_align)
|
|
|
- kbuf.buf_align = bss_align;
|
|
|
+ return 0;
|
|
|
+out:
|
|
|
+ vfree(pi->purgatory_buf);
|
|
|
+ pi->purgatory_buf = NULL;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
|
|
|
- /* Add buffer to segment list */
|
|
|
- ret = kexec_add_buffer(&kbuf);
|
|
|
- if (ret)
|
|
|
- goto out;
|
|
|
- pi->purgatory_load_addr = kbuf.mem;
|
|
|
+/*
|
|
|
+ * kexec_purgatory_setup_sechdrs - prepares the pi->sechdrs buffer.
|
|
|
+ * @pi: Purgatory to be loaded.
|
|
|
+ * @kbuf: Buffer prepared to store purgatory.
|
|
|
+ *
|
|
|
+ * Allocates the memory needed for the buffer. Caller is responsible to free
|
|
|
+ * the memory after use.
|
|
|
+ *
|
|
|
+ * Return: 0 on success, negative errno on error.
|
|
|
+ */
|
|
|
+static int kexec_purgatory_setup_sechdrs(struct purgatory_info *pi,
|
|
|
+ struct kexec_buf *kbuf)
|
|
|
+{
|
|
|
+ unsigned long bss_addr;
|
|
|
+ unsigned long offset;
|
|
|
+ Elf_Shdr *sechdrs;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The section headers in kexec_purgatory are read-only. In order to
|
|
|
+ * have them modifiable make a temporary copy.
|
|
|
+ */
|
|
|
+ sechdrs = vzalloc(pi->ehdr->e_shnum * sizeof(Elf_Shdr));
|
|
|
+ if (!sechdrs)
|
|
|
+ return -ENOMEM;
|
|
|
+ memcpy(sechdrs, (void *)pi->ehdr + pi->ehdr->e_shoff,
|
|
|
+ pi->ehdr->e_shnum * sizeof(Elf_Shdr));
|
|
|
+ pi->sechdrs = sechdrs;
|
|
|
|
|
|
- /* Load SHF_ALLOC sections */
|
|
|
- buf_addr = kbuf.buffer;
|
|
|
- load_addr = curr_load_addr = pi->purgatory_load_addr;
|
|
|
- bss_addr = load_addr + kbuf.bufsz + bss_pad;
|
|
|
+ offset = 0;
|
|
|
+ bss_addr = kbuf->mem + kbuf->bufsz;
|
|
|
+ kbuf->image->start = pi->ehdr->e_entry;
|
|
|
|
|
|
for (i = 0; i < pi->ehdr->e_shnum; i++) {
|
|
|
+ unsigned long align;
|
|
|
+ void *src, *dst;
|
|
|
+
|
|
|
if (!(sechdrs[i].sh_flags & SHF_ALLOC))
|
|
|
continue;
|
|
|
|
|
|
align = sechdrs[i].sh_addralign;
|
|
|
- if (sechdrs[i].sh_type != SHT_NOBITS) {
|
|
|
- curr_load_addr = ALIGN(curr_load_addr, align);
|
|
|
- offset = curr_load_addr - load_addr;
|
|
|
- /* We already modifed ->sh_offset to keep src addr */
|
|
|
- src = (char *) sechdrs[i].sh_offset;
|
|
|
- memcpy(buf_addr + offset, src, sechdrs[i].sh_size);
|
|
|
-
|
|
|
- /* Store load address and source address of section */
|
|
|
- sechdrs[i].sh_addr = curr_load_addr;
|
|
|
-
|
|
|
- /*
|
|
|
- * This section got copied to temporary buffer. Update
|
|
|
- * ->sh_offset accordingly.
|
|
|
- */
|
|
|
- sechdrs[i].sh_offset = (unsigned long)(buf_addr + offset);
|
|
|
-
|
|
|
- /* Advance to the next address */
|
|
|
- curr_load_addr += sechdrs[i].sh_size;
|
|
|
- } else {
|
|
|
+ if (sechdrs[i].sh_type == SHT_NOBITS) {
|
|
|
bss_addr = ALIGN(bss_addr, align);
|
|
|
sechdrs[i].sh_addr = bss_addr;
|
|
|
bss_addr += sechdrs[i].sh_size;
|
|
|
+ continue;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- /* Update entry point based on load address of text section */
|
|
|
- if (entry_sidx >= 0)
|
|
|
- entry += sechdrs[entry_sidx].sh_addr;
|
|
|
+ offset = ALIGN(offset, align);
|
|
|
+ if (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)) {
|
|
|
+ kbuf->image->start -= sechdrs[i].sh_addr;
|
|
|
+ kbuf->image->start += kbuf->mem + offset;
|
|
|
+ }
|
|
|
|
|
|
- /* Make kernel jump to purgatory after shutdown */
|
|
|
- image->start = entry;
|
|
|
+ src = (void *)pi->ehdr + sechdrs[i].sh_offset;
|
|
|
+ dst = pi->purgatory_buf + offset;
|
|
|
+ memcpy(dst, src, sechdrs[i].sh_size);
|
|
|
|
|
|
- /* Used later to get/set symbol values */
|
|
|
- pi->sechdrs = sechdrs;
|
|
|
+ sechdrs[i].sh_addr = kbuf->mem + offset;
|
|
|
+ sechdrs[i].sh_offset = offset;
|
|
|
+ offset += sechdrs[i].sh_size;
|
|
|
+ }
|
|
|
|
|
|
- /*
|
|
|
- * Used later to identify which section is purgatory and skip it
|
|
|
- * from checksumming.
|
|
|
- */
|
|
|
- pi->purgatory_buf = kbuf.buffer;
|
|
|
- return ret;
|
|
|
-out:
|
|
|
- vfree(sechdrs);
|
|
|
- vfree(kbuf.buffer);
|
|
|
- return ret;
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static int kexec_apply_relocations(struct kimage *image)
|
|
|
{
|
|
|
int i, ret;
|
|
|
struct purgatory_info *pi = &image->purgatory_info;
|
|
|
- Elf_Shdr *sechdrs = pi->sechdrs;
|
|
|
+ const Elf_Shdr *sechdrs;
|
|
|
+
|
|
|
+ sechdrs = (void *)pi->ehdr + pi->ehdr->e_shoff;
|
|
|
|
|
|
- /* Apply relocations */
|
|
|
for (i = 0; i < pi->ehdr->e_shnum; i++) {
|
|
|
- Elf_Shdr *section, *symtab;
|
|
|
+ const Elf_Shdr *relsec;
|
|
|
+ const Elf_Shdr *symtab;
|
|
|
+ Elf_Shdr *section;
|
|
|
+
|
|
|
+ relsec = sechdrs + i;
|
|
|
|
|
|
- if (sechdrs[i].sh_type != SHT_RELA &&
|
|
|
- sechdrs[i].sh_type != SHT_REL)
|
|
|
+ if (relsec->sh_type != SHT_RELA &&
|
|
|
+ relsec->sh_type != SHT_REL)
|
|
|
continue;
|
|
|
|
|
|
/*
|
|
@@ -834,12 +865,12 @@ static int kexec_apply_relocations(struct kimage *image)
|
|
|
* symbol table. And ->sh_info contains section header
|
|
|
* index of section to which relocations apply.
|
|
|
*/
|
|
|
- if (sechdrs[i].sh_info >= pi->ehdr->e_shnum ||
|
|
|
- sechdrs[i].sh_link >= pi->ehdr->e_shnum)
|
|
|
+ if (relsec->sh_info >= pi->ehdr->e_shnum ||
|
|
|
+ relsec->sh_link >= pi->ehdr->e_shnum)
|
|
|
return -ENOEXEC;
|
|
|
|
|
|
- section = &sechdrs[sechdrs[i].sh_info];
|
|
|
- symtab = &sechdrs[sechdrs[i].sh_link];
|
|
|
+ section = pi->sechdrs + relsec->sh_info;
|
|
|
+ symtab = sechdrs + relsec->sh_link;
|
|
|
|
|
|
if (!(section->sh_flags & SHF_ALLOC))
|
|
|
continue;
|
|
@@ -856,12 +887,12 @@ static int kexec_apply_relocations(struct kimage *image)
|
|
|
* Respective architecture needs to provide support for applying
|
|
|
* relocations of type SHT_RELA/SHT_REL.
|
|
|
*/
|
|
|
- if (sechdrs[i].sh_type == SHT_RELA)
|
|
|
- ret = arch_kexec_apply_relocations_add(pi->ehdr,
|
|
|
- sechdrs, i);
|
|
|
- else if (sechdrs[i].sh_type == SHT_REL)
|
|
|
- ret = arch_kexec_apply_relocations(pi->ehdr,
|
|
|
- sechdrs, i);
|
|
|
+ if (relsec->sh_type == SHT_RELA)
|
|
|
+ ret = arch_kexec_apply_relocations_add(pi, section,
|
|
|
+ relsec, symtab);
|
|
|
+ else if (relsec->sh_type == SHT_REL)
|
|
|
+ ret = arch_kexec_apply_relocations(pi, section,
|
|
|
+ relsec, symtab);
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
}
|
|
@@ -869,10 +900,18 @@ static int kexec_apply_relocations(struct kimage *image)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-/* Load relocatable purgatory object and relocate it appropriately */
|
|
|
-int kexec_load_purgatory(struct kimage *image, unsigned long min,
|
|
|
- unsigned long max, int top_down,
|
|
|
- unsigned long *load_addr)
|
|
|
+/*
|
|
|
+ * kexec_load_purgatory - Load and relocate the purgatory object.
|
|
|
+ * @image: Image to add the purgatory to.
|
|
|
+ * @kbuf: Memory parameters to use.
|
|
|
+ *
|
|
|
+ * Allocates the memory needed for image->purgatory_info.sechdrs and
|
|
|
+ * image->purgatory_info.purgatory_buf/kbuf->buffer. Caller is responsible
|
|
|
+ * to free the memory after use.
|
|
|
+ *
|
|
|
+ * Return: 0 on success, negative errno on error.
|
|
|
+ */
|
|
|
+int kexec_load_purgatory(struct kimage *image, struct kexec_buf *kbuf)
|
|
|
{
|
|
|
struct purgatory_info *pi = &image->purgatory_info;
|
|
|
int ret;
|
|
@@ -880,55 +919,51 @@ int kexec_load_purgatory(struct kimage *image, unsigned long min,
|
|
|
if (kexec_purgatory_size <= 0)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- if (kexec_purgatory_size < sizeof(Elf_Ehdr))
|
|
|
- return -ENOEXEC;
|
|
|
-
|
|
|
- pi->ehdr = (Elf_Ehdr *)kexec_purgatory;
|
|
|
-
|
|
|
- if (memcmp(pi->ehdr->e_ident, ELFMAG, SELFMAG) != 0
|
|
|
- || pi->ehdr->e_type != ET_REL
|
|
|
- || !elf_check_arch(pi->ehdr)
|
|
|
- || pi->ehdr->e_shentsize != sizeof(Elf_Shdr))
|
|
|
- return -ENOEXEC;
|
|
|
-
|
|
|
- if (pi->ehdr->e_shoff >= kexec_purgatory_size
|
|
|
- || (pi->ehdr->e_shnum * sizeof(Elf_Shdr) >
|
|
|
- kexec_purgatory_size - pi->ehdr->e_shoff))
|
|
|
- return -ENOEXEC;
|
|
|
+ pi->ehdr = (const Elf_Ehdr *)kexec_purgatory;
|
|
|
|
|
|
- ret = __kexec_load_purgatory(image, min, max, top_down);
|
|
|
+ ret = kexec_purgatory_setup_kbuf(pi, kbuf);
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
|
|
|
+ ret = kexec_purgatory_setup_sechdrs(pi, kbuf);
|
|
|
+ if (ret)
|
|
|
+ goto out_free_kbuf;
|
|
|
+
|
|
|
ret = kexec_apply_relocations(image);
|
|
|
if (ret)
|
|
|
goto out;
|
|
|
|
|
|
- *load_addr = pi->purgatory_load_addr;
|
|
|
return 0;
|
|
|
out:
|
|
|
vfree(pi->sechdrs);
|
|
|
pi->sechdrs = NULL;
|
|
|
-
|
|
|
+out_free_kbuf:
|
|
|
vfree(pi->purgatory_buf);
|
|
|
pi->purgatory_buf = NULL;
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static Elf_Sym *kexec_purgatory_find_symbol(struct purgatory_info *pi,
|
|
|
- const char *name)
|
|
|
+/*
|
|
|
+ * 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)
|
|
|
{
|
|
|
- Elf_Sym *syms;
|
|
|
- Elf_Shdr *sechdrs;
|
|
|
- Elf_Ehdr *ehdr;
|
|
|
- int i, k;
|
|
|
+ const Elf_Shdr *sechdrs;
|
|
|
+ const Elf_Ehdr *ehdr;
|
|
|
+ const Elf_Sym *syms;
|
|
|
const char *strtab;
|
|
|
+ int i, k;
|
|
|
|
|
|
- if (!pi->sechdrs || !pi->ehdr)
|
|
|
+ if (!pi->ehdr)
|
|
|
return NULL;
|
|
|
|
|
|
- sechdrs = pi->sechdrs;
|
|
|
ehdr = pi->ehdr;
|
|
|
+ sechdrs = (void *)ehdr + ehdr->e_shoff;
|
|
|
|
|
|
for (i = 0; i < ehdr->e_shnum; i++) {
|
|
|
if (sechdrs[i].sh_type != SHT_SYMTAB)
|
|
@@ -937,8 +972,8 @@ static Elf_Sym *kexec_purgatory_find_symbol(struct purgatory_info *pi,
|
|
|
if (sechdrs[i].sh_link >= ehdr->e_shnum)
|
|
|
/* Invalid strtab section number */
|
|
|
continue;
|
|
|
- strtab = (char *)sechdrs[sechdrs[i].sh_link].sh_offset;
|
|
|
- syms = (Elf_Sym *)sechdrs[i].sh_offset;
|
|
|
+ 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++) {
|
|
@@ -966,7 +1001,7 @@ static Elf_Sym *kexec_purgatory_find_symbol(struct purgatory_info *pi,
|
|
|
void *kexec_purgatory_get_symbol_addr(struct kimage *image, const char *name)
|
|
|
{
|
|
|
struct purgatory_info *pi = &image->purgatory_info;
|
|
|
- Elf_Sym *sym;
|
|
|
+ const Elf_Sym *sym;
|
|
|
Elf_Shdr *sechdr;
|
|
|
|
|
|
sym = kexec_purgatory_find_symbol(pi, name);
|
|
@@ -989,9 +1024,9 @@ void *kexec_purgatory_get_symbol_addr(struct kimage *image, const char *name)
|
|
|
int kexec_purgatory_get_set_symbol(struct kimage *image, const char *name,
|
|
|
void *buf, unsigned int size, bool get_value)
|
|
|
{
|
|
|
- Elf_Sym *sym;
|
|
|
- Elf_Shdr *sechdrs;
|
|
|
struct purgatory_info *pi = &image->purgatory_info;
|
|
|
+ const Elf_Sym *sym;
|
|
|
+ Elf_Shdr *sec;
|
|
|
char *sym_buf;
|
|
|
|
|
|
sym = kexec_purgatory_find_symbol(pi, name);
|
|
@@ -1004,16 +1039,15 @@ int kexec_purgatory_get_set_symbol(struct kimage *image, const char *name,
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- sechdrs = pi->sechdrs;
|
|
|
+ sec = pi->sechdrs + sym->st_shndx;
|
|
|
|
|
|
- if (sechdrs[sym->st_shndx].sh_type == SHT_NOBITS) {
|
|
|
+ if (sec->sh_type == SHT_NOBITS) {
|
|
|
pr_err("symbol %s is in a bss section. Cannot %s\n", name,
|
|
|
get_value ? "get" : "set");
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
|
- sym_buf = (unsigned char *)sechdrs[sym->st_shndx].sh_offset +
|
|
|
- sym->st_value;
|
|
|
+ sym_buf = (char *)pi->purgatory_buf + sec->sh_offset + sym->st_value;
|
|
|
|
|
|
if (get_value)
|
|
|
memcpy((void *)buf, sym_buf, size);
|
|
@@ -1022,3 +1056,174 @@ int kexec_purgatory_get_set_symbol(struct kimage *image, const char *name,
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
+#endif /* CONFIG_ARCH_HAS_KEXEC_PURGATORY */
|
|
|
+
|
|
|
+int crash_exclude_mem_range(struct crash_mem *mem,
|
|
|
+ unsigned long long mstart, unsigned long long mend)
|
|
|
+{
|
|
|
+ int i, j;
|
|
|
+ unsigned long long start, end;
|
|
|
+ struct crash_mem_range temp_range = {0, 0};
|
|
|
+
|
|
|
+ for (i = 0; i < mem->nr_ranges; i++) {
|
|
|
+ start = mem->ranges[i].start;
|
|
|
+ end = mem->ranges[i].end;
|
|
|
+
|
|
|
+ if (mstart > end || mend < start)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Truncate any area outside of range */
|
|
|
+ if (mstart < start)
|
|
|
+ mstart = start;
|
|
|
+ if (mend > end)
|
|
|
+ mend = end;
|
|
|
+
|
|
|
+ /* Found completely overlapping range */
|
|
|
+ if (mstart == start && mend == end) {
|
|
|
+ mem->ranges[i].start = 0;
|
|
|
+ mem->ranges[i].end = 0;
|
|
|
+ if (i < mem->nr_ranges - 1) {
|
|
|
+ /* Shift rest of the ranges to left */
|
|
|
+ for (j = i; j < mem->nr_ranges - 1; j++) {
|
|
|
+ mem->ranges[j].start =
|
|
|
+ mem->ranges[j+1].start;
|
|
|
+ mem->ranges[j].end =
|
|
|
+ mem->ranges[j+1].end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mem->nr_ranges--;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mstart > start && mend < end) {
|
|
|
+ /* Split original range */
|
|
|
+ mem->ranges[i].end = mstart - 1;
|
|
|
+ temp_range.start = mend + 1;
|
|
|
+ temp_range.end = end;
|
|
|
+ } else if (mstart != start)
|
|
|
+ mem->ranges[i].end = mstart - 1;
|
|
|
+ else
|
|
|
+ mem->ranges[i].start = mend + 1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If a split happened, add the split to array */
|
|
|
+ if (!temp_range.end)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* Split happened */
|
|
|
+ if (i == mem->max_nr_ranges - 1)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ /* Location where new range should go */
|
|
|
+ j = i + 1;
|
|
|
+ if (j < mem->nr_ranges) {
|
|
|
+ /* Move over all ranges one slot towards the end */
|
|
|
+ for (i = mem->nr_ranges - 1; i >= j; i--)
|
|
|
+ mem->ranges[i + 1] = mem->ranges[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ mem->ranges[j].start = temp_range.start;
|
|
|
+ mem->ranges[j].end = temp_range.end;
|
|
|
+ mem->nr_ranges++;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int crash_prepare_elf64_headers(struct crash_mem *mem, int kernel_map,
|
|
|
+ void **addr, unsigned long *sz)
|
|
|
+{
|
|
|
+ Elf64_Ehdr *ehdr;
|
|
|
+ Elf64_Phdr *phdr;
|
|
|
+ unsigned long nr_cpus = num_possible_cpus(), nr_phdr, elf_sz;
|
|
|
+ unsigned char *buf;
|
|
|
+ unsigned int cpu, i;
|
|
|
+ unsigned long long notes_addr;
|
|
|
+ unsigned long mstart, mend;
|
|
|
+
|
|
|
+ /* extra phdr for vmcoreinfo elf note */
|
|
|
+ nr_phdr = nr_cpus + 1;
|
|
|
+ nr_phdr += mem->nr_ranges;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * kexec-tools creates an extra PT_LOAD phdr for kernel text mapping
|
|
|
+ * area (for example, ffffffff80000000 - ffffffffa0000000 on x86_64).
|
|
|
+ * I think this is required by tools like gdb. So same physical
|
|
|
+ * memory will be mapped in two elf headers. One will contain kernel
|
|
|
+ * text virtual addresses and other will have __va(physical) addresses.
|
|
|
+ */
|
|
|
+
|
|
|
+ nr_phdr++;
|
|
|
+ elf_sz = sizeof(Elf64_Ehdr) + nr_phdr * sizeof(Elf64_Phdr);
|
|
|
+ elf_sz = ALIGN(elf_sz, ELF_CORE_HEADER_ALIGN);
|
|
|
+
|
|
|
+ buf = vzalloc(elf_sz);
|
|
|
+ if (!buf)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ehdr = (Elf64_Ehdr *)buf;
|
|
|
+ phdr = (Elf64_Phdr *)(ehdr + 1);
|
|
|
+ memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
|
|
|
+ ehdr->e_ident[EI_CLASS] = ELFCLASS64;
|
|
|
+ ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
|
|
|
+ ehdr->e_ident[EI_VERSION] = EV_CURRENT;
|
|
|
+ ehdr->e_ident[EI_OSABI] = ELF_OSABI;
|
|
|
+ memset(ehdr->e_ident + EI_PAD, 0, EI_NIDENT - EI_PAD);
|
|
|
+ ehdr->e_type = ET_CORE;
|
|
|
+ ehdr->e_machine = ELF_ARCH;
|
|
|
+ ehdr->e_version = EV_CURRENT;
|
|
|
+ ehdr->e_phoff = sizeof(Elf64_Ehdr);
|
|
|
+ ehdr->e_ehsize = sizeof(Elf64_Ehdr);
|
|
|
+ ehdr->e_phentsize = sizeof(Elf64_Phdr);
|
|
|
+
|
|
|
+ /* Prepare one phdr of type PT_NOTE for each present cpu */
|
|
|
+ for_each_present_cpu(cpu) {
|
|
|
+ phdr->p_type = PT_NOTE;
|
|
|
+ notes_addr = per_cpu_ptr_to_phys(per_cpu_ptr(crash_notes, cpu));
|
|
|
+ phdr->p_offset = phdr->p_paddr = notes_addr;
|
|
|
+ phdr->p_filesz = phdr->p_memsz = sizeof(note_buf_t);
|
|
|
+ (ehdr->e_phnum)++;
|
|
|
+ phdr++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Prepare one PT_NOTE header for vmcoreinfo */
|
|
|
+ phdr->p_type = PT_NOTE;
|
|
|
+ phdr->p_offset = phdr->p_paddr = paddr_vmcoreinfo_note();
|
|
|
+ phdr->p_filesz = phdr->p_memsz = VMCOREINFO_NOTE_SIZE;
|
|
|
+ (ehdr->e_phnum)++;
|
|
|
+ phdr++;
|
|
|
+
|
|
|
+ /* Prepare PT_LOAD type program header for kernel text region */
|
|
|
+ if (kernel_map) {
|
|
|
+ phdr->p_type = PT_LOAD;
|
|
|
+ phdr->p_flags = PF_R|PF_W|PF_X;
|
|
|
+ phdr->p_vaddr = (Elf64_Addr)_text;
|
|
|
+ phdr->p_filesz = phdr->p_memsz = _end - _text;
|
|
|
+ phdr->p_offset = phdr->p_paddr = __pa_symbol(_text);
|
|
|
+ ehdr->e_phnum++;
|
|
|
+ phdr++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Go through all the ranges in mem->ranges[] and prepare phdr */
|
|
|
+ for (i = 0; i < mem->nr_ranges; i++) {
|
|
|
+ mstart = mem->ranges[i].start;
|
|
|
+ mend = mem->ranges[i].end;
|
|
|
+
|
|
|
+ phdr->p_type = PT_LOAD;
|
|
|
+ phdr->p_flags = PF_R|PF_W|PF_X;
|
|
|
+ phdr->p_offset = mstart;
|
|
|
+
|
|
|
+ phdr->p_paddr = mstart;
|
|
|
+ phdr->p_vaddr = (unsigned long long) __va(mstart);
|
|
|
+ phdr->p_filesz = phdr->p_memsz = mend - mstart + 1;
|
|
|
+ phdr->p_align = 0;
|
|
|
+ ehdr->e_phnum++;
|
|
|
+ phdr++;
|
|
|
+ pr_debug("Crash PT_LOAD elf header. phdr=%p vaddr=0x%llx, paddr=0x%llx, sz=0x%llx e_phnum=%d p_offset=0x%llx\n",
|
|
|
+ phdr, phdr->p_vaddr, phdr->p_paddr, phdr->p_filesz,
|
|
|
+ ehdr->e_phnum, phdr->p_offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ *addr = buf;
|
|
|
+ *sz = elf_sz;
|
|
|
+ return 0;
|
|
|
+}
|