The first PT_LOAD segment, which is assumed to be "text" code, in vmlinux
will be loaded at the offset of TEXT_OFFSET from the begining of system
memory. The other PT_LOAD segments are placed relative to the first one.

Regarding kernel verification, since there is no standard way to contain
a signature within elf binary, we follow PowerPC's (not yet upstreamed)
approach, that is, appending a signature right after the kernel binary
itself like module signing.
This way, the signature can be easily retrieved and verified with
verify_pkcs7_signature().

We can sign the kernel with sign-file command.

Unlike PowerPC, we don't support ima-based kexec for now since arm64
doesn't have any secure solution for system appraisal at this moment.

Signed-off-by: AKASHI Takahiro <[email protected]>
Cc: Catalin Marinas <[email protected]>
Cc: Will Deacon <[email protected]>
---
 arch/arm64/Kconfig                     |   8 ++
 arch/arm64/include/asm/kexec_file.h    |   1 +
 arch/arm64/kernel/Makefile             |   1 +
 arch/arm64/kernel/kexec_elf.c          | 216 +++++++++++++++++++++++++++++++++
 arch/arm64/kernel/machine_kexec_file.c |   3 +
 5 files changed, 229 insertions(+)
 create mode 100644 arch/arm64/kernel/kexec_elf.c

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index c8f603700bdd..94021e66b826 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -772,11 +772,19 @@ config KEXEC_FILE_IMAGE_FMT
        ---help---
          Select this option to enable 'Image' kernel loading.
 
+config KEXEC_FILE_ELF_FMT
+       bool "Enable vmlinux/elf support"
+       depends on KEXEC_FILE
+       select KEXEC_FILE_ELF
+       ---help---
+         Select this option to enable 'vmlinux' kernel loading.
+
 config KEXEC_VERIFY_SIG
        bool "Verify kernel signature during kexec_file_load() syscall"
        depends on KEXEC_FILE
        select SYSTEM_DATA_VERIFICATION
        select SIGNED_PE_FILE_VERIFICATION if KEXEC_FILE_IMAGE_FMT
+       select MODULE_SIG_FORMAT if KEXEC_FILE_ELF_FMT
        ---help---
          This option makes kernel signature verification mandatory for
          the kexec_file_load() syscall.
diff --git a/arch/arm64/include/asm/kexec_file.h 
b/arch/arm64/include/asm/kexec_file.h
index 5df899aa0d2e..eaf2adc1121c 100644
--- a/arch/arm64/include/asm/kexec_file.h
+++ b/arch/arm64/include/asm/kexec_file.h
@@ -2,6 +2,7 @@
 #define _ASM_KEXEC_FILE_H
 
 extern struct kexec_file_ops kexec_image_ops;
+extern struct kexec_file_ops kexec_elf64_ops;
 
 /**
  * struct arm64_image_header - arm64 kernel image header.
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index a1161bab6810..1463337160ea 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -52,6 +52,7 @@ arm64-obj-$(CONFIG_KEXEC_CORE)                += 
machine_kexec.o relocate_kernel.o    \
                                           cpu-reset.o
 arm64-obj-$(CONFIG_KEXEC_FILE)         += machine_kexec_file.o
 arm64-obj-$(CONFIG_KEXEC_FILE_IMAGE_FMT)       += kexec_image.o
+arm64-obj-$(CONFIG_KEXEC_FILE_ELF_FMT) += kexec_elf.o
 arm64-obj-$(CONFIG_ARM64_RELOC_TEST)   += arm64-reloc-test.o
 arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
 arm64-obj-$(CONFIG_CRASH_DUMP)         += crash_dump.o
diff --git a/arch/arm64/kernel/kexec_elf.c b/arch/arm64/kernel/kexec_elf.c
new file mode 100644
index 000000000000..7bd3c1e1f65a
--- /dev/null
+++ b/arch/arm64/kernel/kexec_elf.c
@@ -0,0 +1,216 @@
+/*
+ * Kexec vmlinux loader
+
+ * Copyright (C) 2017 Linaro Limited
+ * Authors: AKASHI Takahiro <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt)    "kexec_file(elf): " fmt
+
+#include <linux/elf.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/kexec.h>
+#include <linux/module_signature.h>
+#include <linux/types.h>
+#include <linux/verification.h>
+#include <asm/byteorder.h>
+#include <asm/kexec_file.h>
+#include <asm/memory.h>
+
+static int elf64_probe(const char *buf, unsigned long len)
+{
+       struct elfhdr ehdr;
+
+       /* Check for magic and architecture */
+       memcpy(&ehdr, buf, sizeof(ehdr));
+       if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) ||
+               (elf16_to_cpu(&ehdr, ehdr.e_machine) != EM_AARCH64))
+               return -ENOEXEC;
+
+       return 0;
+}
+
+static int elf_exec_load(struct kimage *image, struct elfhdr *ehdr,
+                        struct elf_info *elf_info,
+                        unsigned long *kernel_load_addr)
+{
+       struct kexec_buf kbuf;
+       const struct elf_phdr *phdr;
+       const struct arm64_image_header *h;
+       unsigned long text_offset, rand_offset;
+       unsigned long page_offset, phys_offset;
+       int first_segment, i, ret = -ENOEXEC;
+
+       kbuf.image = image;
+       if (image->type == KEXEC_TYPE_CRASH) {
+               kbuf.buf_min = crashk_res.start;
+               kbuf.buf_max = crashk_res.end + 1;
+       } else {
+               kbuf.buf_min = 0;
+               kbuf.buf_max = ULONG_MAX;
+       }
+       kbuf.top_down = 0;
+
+       /* Load PT_LOAD segments. */
+       for (i = 0, first_segment = 1; i < ehdr->e_phnum; i++) {
+               phdr = &elf_info->proghdrs[i];
+               if (phdr->p_type != PT_LOAD)
+                       continue;
+
+               kbuf.buffer = (void *) elf_info->buffer + phdr->p_offset;
+               kbuf.bufsz = min(phdr->p_filesz, phdr->p_memsz);
+               kbuf.memsz = phdr->p_memsz;
+               kbuf.buf_align = phdr->p_align;
+
+               if (first_segment) {
+                       /*
+                        * Identify TEXT_OFFSET:
+                        * When CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET=y the image
+                        * header could be offset in the elf segment. The linker
+                        * script sets ehdr->e_entry to the start of text.
+                        *
+                        * NOTE: In v3.16 or older, h->text_offset is 0,
+                        * so use the default, 0x80000
+                        */
+                       rand_offset = ehdr->e_entry - phdr->p_vaddr;
+                       h = (struct arm64_image_header *)
+                                       (elf_info->buffer + phdr->p_offset +
+                                       rand_offset);
+
+                       if (!arm64_header_check_magic(h))
+                               goto out;
+
+                       if (h->image_size)
+                               text_offset = le64_to_cpu(h->text_offset);
+                       else
+                               text_offset = 0x80000;
+
+                       /* Adjust kernel segment with TEXT_OFFSET */
+                       kbuf.memsz += text_offset - rand_offset;
+
+                       ret = kexec_add_buffer(&kbuf);
+                       if (ret)
+                               goto out;
+
+                       image->segment[image->nr_segments - 1].mem
+                                       += text_offset - rand_offset;
+                       image->segment[image->nr_segments - 1].memsz
+                                       -= text_offset - rand_offset;
+
+                       *kernel_load_addr = kbuf.mem + text_offset;
+
+                       /* for succeeding segmemts */
+                       page_offset = ALIGN_DOWN(phdr->p_vaddr, SZ_2M);
+                       phys_offset = kbuf.mem;
+
+                       first_segment = 0;
+               } else {
+                       /* Calculate physical address */
+                       kbuf.mem = phdr->p_vaddr - page_offset + phys_offset;
+
+                       ret = kexec_add_segment(&kbuf);
+                       if (ret)
+                               goto out;
+               }
+       }
+
+out:
+       return ret;
+}
+
+static void *elf64_load(struct kimage *image, char *kernel_buf,
+                       unsigned long kernel_len, char *initrd,
+                       unsigned long initrd_len, char *cmdline,
+                       unsigned long cmdline_len)
+{
+       struct elfhdr ehdr;
+       struct elf_info elf_info;
+       unsigned long kernel_load_addr;
+       int ret;
+
+       /* Create elf core header segment */
+       ret = load_crashdump_segments(image);
+       if (ret)
+               goto out;
+
+       /* Load the kernel */
+       ret = build_elf_exec_info(kernel_buf, kernel_len, &ehdr, &elf_info);
+       if (ret)
+               goto out;
+
+       ret = elf_exec_load(image, &ehdr, &elf_info, &kernel_load_addr);
+       if (ret)
+               goto out;
+       pr_debug("Loaded the kernel at 0x%lx\n", kernel_load_addr);
+
+       /* Load additional data */
+       ret = load_other_segments(image, kernel_load_addr,
+                               initrd, initrd_len, cmdline, cmdline_len);
+
+out:
+       elf_free_info(&elf_info);
+
+       return ERR_PTR(ret);
+}
+
+#ifdef CONFIG_KEXEC_VERIFY_SIG
+/*
+ * The file format is the exact same as module signing:
+ *   <kernel> := <Image> + <signature part> + <marker>
+ *   <signature part> := <signature data> + <struct module_signature>
+ */
+static int elf64_verify_sig(const char *kernel, unsigned long kernel_len)
+{
+       const size_t marker_len = sizeof(MODULE_SIG_STRING) - 1;
+       const struct module_signature *sig;
+       size_t file_len = kernel_len;
+       size_t sig_len;
+       const void *p;
+       int rc;
+
+       if (kernel_len <= marker_len + sizeof(*sig))
+               return -ENOENT;
+
+       /* Check for marker */
+       p = kernel + kernel_len - marker_len;
+       if (memcmp(p, MODULE_SIG_STRING, marker_len)) {
+               pr_err("probably the kernel is not signed.\n");
+               return -ENOENT;
+       }
+
+       /* Validate signature */
+       sig = (const struct module_signature *) (p - sizeof(*sig));
+       file_len -= marker_len;
+
+       rc = validate_module_sig(sig, kernel_len - marker_len);
+       if (rc) {
+               pr_err("signature is not valid\n");
+               return rc;
+       }
+
+       /* Verify kernel with signature */
+       sig_len = be32_to_cpu(sig->sig_len);
+       p -= sig_len + sizeof(*sig);
+       file_len -= sig_len + sizeof(*sig);
+
+       rc = verify_pkcs7_signature(kernel, p - (void *)kernel, p, sig_len,
+                                       NULL, VERIFYING_MODULE_SIGNATURE,
+                                       NULL, NULL);
+
+       return rc;
+}
+#endif
+
+struct kexec_file_ops kexec_elf64_ops = {
+       .probe = elf64_probe,
+       .load = elf64_load,
+#ifdef CONFIG_KEXEC_VERIFY_SIG
+       .verify_sig = elf64_verify_sig,
+#endif
+};
diff --git a/arch/arm64/kernel/machine_kexec_file.c 
b/arch/arm64/kernel/machine_kexec_file.c
index ab3b19d51727..cb1f24d98f87 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -31,6 +31,9 @@ static struct kexec_file_ops *kexec_file_loaders[] = {
 #ifdef CONFIG_KEXEC_FILE_IMAGE_FMT
        &kexec_image_ops,
 #endif
+#ifdef CONFIG_KEXEC_FILE_ELF_FMT
+       &kexec_elf64_ops,
+#endif
 };
 
 int arch_kexec_kernel_image_probe(struct kimage *image, void *buf,
-- 
2.14.1

Reply via email to