aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/arm/boot.c6
-rw-r--r--hw/core/loader.c91
-rw-r--r--include/hw/loader.h19
3 files changed, 116 insertions, 0 deletions
diff --git a/hw/arm/boot.c b/hw/arm/boot.c
index 1e021c4a34..50e5141116 100644
--- a/hw/arm/boot.c
+++ b/hw/arm/boot.c
@@ -926,6 +926,12 @@ static uint64_t load_aarch64_image(const char *filename, hwaddr mem_base,
return -1;
}
size = len;
+
+ /* Unpack the image if it is a EFI zboot image */
+ if (unpack_efi_zboot_image(&buffer, &size) < 0) {
+ g_free(buffer);
+ return -1;
+ }
}
/* check the arm64 magic header value -- very old kernels may not have it */
diff --git a/hw/core/loader.c b/hw/core/loader.c
index 173f8f67f6..cd53235fed 100644
--- a/hw/core/loader.c
+++ b/hw/core/loader.c
@@ -857,6 +857,97 @@ ssize_t load_image_gzipped(const char *filename, hwaddr addr, uint64_t max_sz)
return bytes;
}
+/* The PE/COFF MS-DOS stub magic number */
+#define EFI_PE_MSDOS_MAGIC "MZ"
+
+/*
+ * The Linux header magic number for a EFI PE/COFF
+ * image targetting an unspecified architecture.
+ */
+#define EFI_PE_LINUX_MAGIC "\xcd\x23\x82\x81"
+
+/*
+ * Bootable Linux kernel images may be packaged as EFI zboot images, which are
+ * self-decompressing executables when loaded via EFI. The compressed payload
+ * can also be extracted from the image and decompressed by a non-EFI loader.
+ *
+ * The de facto specification for this format is at the following URL:
+ *
+ * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/firmware/efi/libstub/zboot-header.S
+ *
+ * This definition is based on Linux upstream commit 29636a5ce87beba.
+ */
+struct linux_efi_zboot_header {
+ uint8_t msdos_magic[2]; /* PE/COFF 'MZ' magic number */
+ uint8_t reserved0[2];
+ uint8_t zimg[4]; /* "zimg" for Linux EFI zboot images */
+ uint32_t payload_offset; /* LE offset to compressed payload */
+ uint32_t payload_size; /* LE size of the compressed payload */
+ uint8_t reserved1[8];
+ char compression_type[32]; /* Compression type, NUL terminated */
+ uint8_t linux_magic[4]; /* Linux header magic */
+ uint32_t pe_header_offset; /* LE offset to the PE header */
+};
+
+/*
+ * Check whether *buffer points to a Linux EFI zboot image in memory.
+ *
+ * If it does, attempt to decompress it to a new buffer, and free the old one.
+ * If any of this fails, return an error to the caller.
+ *
+ * If the image is not a Linux EFI zboot image, do nothing and return success.
+ */
+ssize_t unpack_efi_zboot_image(uint8_t **buffer, int *size)
+{
+ const struct linux_efi_zboot_header *header;
+ uint8_t *data = NULL;
+ int ploff, plsize;
+ ssize_t bytes;
+
+ /* ignore if this is too small to be a EFI zboot image */
+ if (*size < sizeof(*header)) {
+ return 0;
+ }
+
+ header = (struct linux_efi_zboot_header *)*buffer;
+
+ /* ignore if this is not a Linux EFI zboot image */
+ if (memcmp(&header->msdos_magic, EFI_PE_MSDOS_MAGIC, 2) != 0 ||
+ memcmp(&header->zimg, "zimg", 4) != 0 ||
+ memcmp(&header->linux_magic, EFI_PE_LINUX_MAGIC, 4) != 0) {
+ return 0;
+ }
+
+ if (strcmp(header->compression_type, "gzip") != 0) {
+ fprintf(stderr,
+ "unable to handle EFI zboot image with \"%.*s\" compression\n",
+ (int)sizeof(header->compression_type) - 1,
+ header->compression_type);
+ return -1;
+ }
+
+ ploff = ldl_le_p(&header->payload_offset);
+ plsize = ldl_le_p(&header->payload_size);
+
+ if (ploff < 0 || plsize < 0 || ploff + plsize > *size) {
+ fprintf(stderr, "unable to handle corrupt EFI zboot image\n");
+ return -1;
+ }
+
+ data = g_malloc(LOAD_IMAGE_MAX_GUNZIP_BYTES);
+ bytes = gunzip(data, LOAD_IMAGE_MAX_GUNZIP_BYTES, *buffer + ploff, plsize);
+ if (bytes < 0) {
+ fprintf(stderr, "failed to decompress EFI zboot image\n");
+ g_free(data);
+ return -1;
+ }
+
+ g_free(*buffer);
+ *buffer = g_realloc(data, bytes);
+ *size = bytes;
+ return bytes;
+}
+
/*
* Functions for reboot-persistent memory regions.
* - used for vga bios and option roms.
diff --git a/include/hw/loader.h b/include/hw/loader.h
index 1384796a4b..c4c14170ea 100644
--- a/include/hw/loader.h
+++ b/include/hw/loader.h
@@ -86,6 +86,25 @@ ssize_t load_image_gzipped_buffer(const char *filename, uint64_t max_sz,
uint8_t **buffer);
ssize_t load_image_gzipped(const char *filename, hwaddr addr, uint64_t max_sz);
+/**
+ * unpack_efi_zboot_image:
+ * @buffer: pointer to a variable holding the address of a buffer containing the
+ * image
+ * @size: pointer to a variable holding the size of the buffer
+ *
+ * Check whether the buffer contains a EFI zboot image, and if it does, extract
+ * the compressed payload and decompress it into a new buffer. If successful,
+ * the old buffer is freed, and the *buffer and size variables pointed to by the
+ * function arguments are updated to refer to the newly populated buffer.
+ *
+ * Returns 0 if the image could not be identified as a EFI zboot image.
+ * Returns -1 if the buffer contents were identified as a EFI zboot image, but
+ * unpacking failed for any reason.
+ * Returns the size of the decompressed payload if decompression was performed
+ * successfully.
+ */
+ssize_t unpack_efi_zboot_image(uint8_t **buffer, int *size);
+
#define ELF_LOAD_FAILED -1
#define ELF_LOAD_NOT_ELF -2
#define ELF_LOAD_WRONG_ARCH -3