diff mbox series

[5/6] arm64: kexec: Introduce zboot image loader

Message ID 20230306030305.15595-6-kernelfans@gmail.com (mailing list archive)
State New, archived
Headers show
Series arm64: make kexec_file able to load zboot image | expand

Commit Message

Pingfan Liu March 6, 2023, 3:03 a.m. UTC
The EFI zboot kernel format can decompress and boot the vmlinux, but it
relies on the EFI service, which is not available when kexec jumps to
the new entry.

To tackle this issue, parsing zboot image and decompressing the Image.gz
part at the file loading time. But this way, in essential, the kexec
boots up Image.

As for decompression, it can be done either in the user space or the
kernel. But due to the signature verification in kernel, it should be
achieved in the kernel space.

Besides this, kexec faces a situation a little different from
efi_zboot_entry(), where the latter has knowledge of the system memory
through EFI services, while kexec can do it through the kernel's mm.

Signed-off-by: Pingfan Liu <kernelfans@gmail.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Nick Terrell <terrelln@fb.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Mimi Zohar <zohar@linux.ibm.com>
Cc: "Naveen N. Rao" <naveen.n.rao@linux.vnet.ibm.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Michal Suchanek <msuchanek@suse.de>
Cc: Baoquan He <bhe@redhat.com>
Cc: kexec@lists.infradead.org
To: linux-arm-kernel@lists.infradead.org
---
 arch/arm64/include/asm/kexec.h         |   2 +
 arch/arm64/kernel/Makefile             |   2 +-
 arch/arm64/kernel/kexec_zboot_image.c  | 186 +++++++++++++++++++++++++
 arch/arm64/kernel/machine_kexec.c      |   1 +
 arch/arm64/kernel/machine_kexec_file.c |   1 +
 include/linux/zboot.h                  |  26 ++++
 6 files changed, 217 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm64/kernel/kexec_zboot_image.c
 create mode 100644 include/linux/zboot.h
diff mbox series

Patch

diff --git a/arch/arm64/include/asm/kexec.h b/arch/arm64/include/asm/kexec.h
index 3f3d5a6830b7..8b820088323c 100644
--- a/arch/arm64/include/asm/kexec.h
+++ b/arch/arm64/include/asm/kexec.h
@@ -114,6 +114,7 @@  void arch_kexec_unprotect_crashkres(void);
 
 struct kimage_arch {
 	void *dtb;
+	void *decompressed_kernel;
 	phys_addr_t dtb_mem;
 	phys_addr_t kern_reloc;
 	phys_addr_t el2_vectors;
@@ -126,6 +127,7 @@  struct kimage_arch {
 
 #ifdef CONFIG_KEXEC_FILE
 extern const struct kexec_file_ops kexec_raw_ops;
+extern const struct kexec_file_ops kexec_zboot_ops;
 
 int arch_kimage_file_post_load_cleanup(struct kimage *image);
 #define arch_kimage_file_post_load_cleanup arch_kimage_file_post_load_cleanup
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 99b52710606a..ec4d3c17ef70 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -63,7 +63,7 @@  obj-$(CONFIG_HIBERNATION)		+= hibernate.o hibernate-asm.o
 obj-$(CONFIG_ELF_CORE)			+= elfcore.o
 obj-$(CONFIG_KEXEC_CORE)		+= machine_kexec.o relocate_kernel.o	\
 					   cpu-reset.o
-obj-$(CONFIG_KEXEC_FILE)		+= machine_kexec_file.o kexec_raw_image.o
+obj-$(CONFIG_KEXEC_FILE)		+= machine_kexec_file.o kexec_raw_image.o kexec_zboot_image.o
 obj-$(CONFIG_ARM64_RELOC_TEST)		+= arm64-reloc-test.o
 arm64-reloc-test-y := reloc_test_core.o reloc_test_syms.o
 obj-$(CONFIG_CRASH_DUMP)		+= crash_dump.o
diff --git a/arch/arm64/kernel/kexec_zboot_image.c b/arch/arm64/kernel/kexec_zboot_image.c
new file mode 100644
index 000000000000..4629091666b7
--- /dev/null
+++ b/arch/arm64/kernel/kexec_zboot_image.c
@@ -0,0 +1,186 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kexec zboot image loader.
+ * Code is based on kexec_raw_image.c
+ */
+
+#define pr_fmt(fmt)	"kexec_file(zboot): " fmt
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/kexec.h>
+#include <linux/pe.h>
+#include <linux/string.h>
+#include <asm/byteorder.h>
+#include <asm/cpufeature.h>
+#include <asm/image.h>
+#include <asm/memory.h>
+#include <linux/zboot.h>
+#include <linux/decompress/generic.h>
+
+static int zboot_image_probe(const char *kernel_buf, unsigned long kernel_len)
+{
+	struct zboot_image_header *h =
+		(struct zboot_image_header *)(kernel_buf);
+
+	if (!h || (kernel_len < sizeof(*h)))
+		return -EINVAL;
+
+	if (memcmp(&h->magic, (char *)MZ_MAGIC, sizeof(h->magic)))
+		return -EINVAL;
+	if (strncmp(h->zimg, "zimg", 4))
+		return -EINVAL;
+
+	return 0;
+}
+
+static void error(char *x)
+{
+	pr_err("%s\n", x);
+}
+
+static void *zboot_image_load(struct kimage *image,
+				char *kernel, unsigned long kernel_len,
+				char *initrd, unsigned long initrd_len,
+				char *cmdline, unsigned long cmdline_len)
+{
+	struct zboot_image_header *zh;
+	struct arm64_image_header *h;
+	char *decompressed_buf;
+	u32 sz;
+	long out_sz;
+	u64 flags, value;
+	bool be_image, be_kernel;
+	struct kexec_buf kbuf;
+	unsigned long text_offset, kernel_segment_number;
+	struct kexec_segment *kernel_segment;
+	int ret;
+	decompress_fn decompressor;
+
+	zh = (struct zboot_image_header *)kernel;
+	/*
+	 * zboot has SizeOfCode, SizeOfImage, SizeOfHeaders appended at the end. And
+	 * each occupies 4 bytes.
+	 */
+	sz = *(u32 *)(kernel + zh->gzdata_offset + zh->gzdata_size + 4);
+	out_sz = sz;
+	/* freed in machine_kexec_post_load() */
+	decompressed_buf = kmalloc(sz, GFP_KERNEL);
+	if (!decompressed_buf) {
+		pr_info("Can not get enough memory to decompress the zboot image\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	decompressor = decompress_method_by_name(zh->comp_type);
+	if (!decompressor) {
+		pr_info("Invalid compress-format:%s\n", zh->comp_type);
+		ret = -EINVAL;
+		goto err;
+	}
+	ret = decompressor(kernel + zh->gzdata_offset, zh->gzdata_size, NULL, NULL,
+			   (void *)decompressed_buf, &out_sz, error);
+
+	if (ret) {
+		pr_info("Fail to decompress the zboot image\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	h = (struct arm64_image_header *)decompressed_buf;
+	if (!h->image_size) {
+		kfree(decompressed_buf);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* Check cpu features */
+	flags = le64_to_cpu(h->flags);
+	be_image = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_BE);
+	be_kernel = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN);
+	if ((be_image != be_kernel) && !system_supports_mixed_endian()) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	value = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_PAGE_SIZE);
+	if (((value == ARM64_IMAGE_FLAG_PAGE_SIZE_4K) &&
+			!system_supports_4kb_granule()) ||
+	    ((value == ARM64_IMAGE_FLAG_PAGE_SIZE_64K) &&
+			!system_supports_64kb_granule()) ||
+	    ((value == ARM64_IMAGE_FLAG_PAGE_SIZE_16K) &&
+			!system_supports_16kb_granule())) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* Load the kernel */
+	kbuf.image = image;
+	kbuf.buf_min = 0;
+	kbuf.buf_max = ULONG_MAX;
+	kbuf.top_down = false;
+
+	kbuf.buffer = decompressed_buf;
+	kbuf.bufsz = sz;
+	kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+	kbuf.memsz = le64_to_cpu(h->image_size);
+	text_offset = le64_to_cpu(h->text_offset);
+	kbuf.buf_align = MIN_KIMG_ALIGN;
+
+	/* Adjust kernel segment with TEXT_OFFSET */
+	kbuf.memsz += text_offset;
+
+	kernel_segment_number = image->nr_segments;
+
+	/*
+	 * The location of the kernel segment may make it impossible to satisfy
+	 * the other segment requirements, so we try repeatedly to find a
+	 * location that will work.
+	 */
+	while ((ret = kexec_add_buffer(&kbuf)) == 0) {
+		/* Try to load additional data */
+		kernel_segment = &image->segment[kernel_segment_number];
+		ret = load_other_segments(image, kernel_segment->mem,
+					  kernel_segment->memsz, initrd,
+					  initrd_len, cmdline);
+		if (!ret)
+			break;
+
+		/*
+		 * We couldn't find space for the other segments; erase the
+		 * kernel segment and try the next available hole.
+		 */
+		image->nr_segments -= 1;
+		kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
+		kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
+	}
+
+	if (ret) {
+		pr_err("Could not find any suitable kernel location!");
+		goto err;
+	}
+
+	kernel_segment = &image->segment[kernel_segment_number];
+	kernel_segment->mem += text_offset;
+	kernel_segment->memsz -= text_offset;
+	image->start = kernel_segment->mem;
+	image->arch.decompressed_kernel = decompressed_buf;
+
+	pr_debug("Loaded kernel at 0x%lx bufsz=0x%lx memsz=0x%lx\n",
+				kernel_segment->mem, kbuf.bufsz,
+				kernel_segment->memsz);
+
+	return NULL;
+
+err:
+	kfree(decompressed_buf);
+	return ERR_PTR(ret);
+}
+
+const struct kexec_file_ops kexec_zboot_ops = {
+	.probe = zboot_image_probe,
+	.load = zboot_image_load,
+#ifdef CONFIG_KEXEC_IMAGE_VERIFY_SIG
+	.verify_sig = kexec_kernel_verify_pe_sig,
+#endif
+};
diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c
index ce3d40120f72..866c1c27a9ea 100644
--- a/arch/arm64/kernel/machine_kexec.c
+++ b/arch/arm64/kernel/machine_kexec.c
@@ -166,6 +166,7 @@  int machine_kexec_post_load(struct kimage *kimage)
 	icache_inval_pou((uintptr_t)reloc_code,
 			 (uintptr_t)reloc_code + reloc_size);
 	kexec_image_info(kimage);
+	kfree(kimage->arch.decompressed_kernel);
 
 	return 0;
 }
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index 0738020507d1..02d7e4004cfb 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -24,6 +24,7 @@ 
 
 const struct kexec_file_ops * const kexec_file_loaders[] = {
 	&kexec_raw_ops,
+	&kexec_zboot_ops,
 	NULL
 };
 
diff --git a/include/linux/zboot.h b/include/linux/zboot.h
new file mode 100644
index 000000000000..abefcb92e1ad
--- /dev/null
+++ b/include/linux/zboot.h
@@ -0,0 +1,26 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef ZBOOT_H
+#define ZBOOT_H
+
+struct zboot_image_header {
+	union {
+		struct {
+			u32 magic;
+			/* image type, .ascii "zimg" */
+			char zimg[4];
+			s32 gzdata_offset;
+			s32 gzdata_size;
+			s32 reserved[2];
+			/* compression type, .asciz */
+			char comp_type[];
+		};
+		struct {
+			char pad[56];
+		};
+	};
+	/* 0x818223cd */
+	u32 linux_pe_magic;
+	s32 pe_header_offset;
+} __packed;
+
+#endif