diff mbox series

[PULL,23/49] hw/core: Add Enclave Image Format (EIF) related helpers

Message ID 20241031175214.214455-24-pbonzini@redhat.com (mailing list archive)
State New
Headers show
Series [PULL,01/49] stubs: avoid duplicate symbols in libqemuutil.a | expand

Commit Message

Paolo Bonzini Oct. 31, 2024, 5:51 p.m. UTC
From: Dorjoy Chowdhury <dorjoychy111@gmail.com>

An EIF (Enclave Image Format)[1] file is used to boot an AWS nitro
enclave[2] virtual machine. The EIF file contains the necessary kernel,
cmdline, ramdisk(s) sections to boot.

Some helper functions have been introduced for extracting the necessary
sections from an EIF file and then writing them to temporary files as
well as computing SHA384 hashes from the section data. These will be
used in the following commit to add support for nitro-enclave machine
type in QEMU.

The files added in this commit are not compiled yet but will be added
to the hw/core/meson.build file in the following commit where
CONFIG_NITRO_ENCLAVE will be introduced.

[1] https://github.com/aws/aws-nitro-enclaves-image-format
[2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html

Signed-off-by: Dorjoy Chowdhury <dorjoychy111@gmail.com>
Reviewed-by: Alexander Graf <graf@amazon.com>
Link: https://lore.kernel.org/r/20241008211727.49088-4-dorjoychy111@gmail.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 MAINTAINERS         |   7 +
 meson.build         |   1 +
 hw/core/eif.h       |  22 ++
 hw/core/eif.c       | 719 ++++++++++++++++++++++++++++++++++++++++++++
 Kconfig.host        |   3 +
 hw/core/Kconfig     |   4 +
 hw/core/meson.build |   1 +
 7 files changed, 757 insertions(+)
 create mode 100644 hw/core/eif.h
 create mode 100644 hw/core/eif.c

Comments

Peter Maydell Nov. 5, 2024, 11:43 a.m. UTC | #1
On Thu, 31 Oct 2024 at 17:54, Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> From: Dorjoy Chowdhury <dorjoychy111@gmail.com>
>
> An EIF (Enclave Image Format)[1] file is used to boot an AWS nitro
> enclave[2] virtual machine. The EIF file contains the necessary kernel,
> cmdline, ramdisk(s) sections to boot.
>
> Some helper functions have been introduced for extracting the necessary
> sections from an EIF file and then writing them to temporary files as
> well as computing SHA384 hashes from the section data. These will be
> used in the following commit to add support for nitro-enclave machine
> type in QEMU.
>
> The files added in this commit are not compiled yet but will be added
> to the hw/core/meson.build file in the following commit where
> CONFIG_NITRO_ENCLAVE will be introduced.
>
> [1] https://github.com/aws/aws-nitro-enclaves-image-format
> [2] https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html
>
> Signed-off-by: Dorjoy Chowdhury <dorjoychy111@gmail.com>
> Reviewed-by: Alexander Graf <graf@amazon.com>
> Link: https://lore.kernel.org/r/20241008211727.49088-4-dorjoychy111@gmail.com
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

Hi; Coverity raises a couple of potential issues with the
read_eif_file() function in this commit, which are both
"Coverity assumes the file we're reading is untrusted and is
unsure that we're correctly sanitizing data from it before use".
Could somebody who understands the use case here check whether
these need addressing?

CID 1564855 "overflowed integer argument" --
basically eif_header.section_offsets[i] is from the data
file and is a uint64_t, but we pass it to fseek() in
read_eif_file. fseek() takes a signed offset, so if the
section_offset in the file header is very large it will
be unexpectedly treated as a negative number.

CID 1564859 "untrusted value as argument" --
in the "default" case for handling a section type
we call g_malloc() with the uint64_t size from the header.
If the file data is bogus we might try to allocate a
huge amount of memory, which will cause g_malloc() to
kill QEMU.

thanks
-- PMM
Paolo Bonzini Nov. 5, 2024, 12:51 p.m. UTC | #2
On Tue, Nov 5, 2024 at 12:44 PM Peter Maydell <peter.maydell@linaro.org> wrote:
> Hi; Coverity raises a couple of potential issues with the
> read_eif_file() function in this commit, which are both
> "Coverity assumes the file we're reading is untrusted and is
> unsure that we're correctly sanitizing data from it before use".
> Could somebody who understands the use case here check whether
> these need addressing?

Both are reasonable to fix, even if the use case would not make them
security sensitive. I'll prepare and send a patch.

Paolo

> CID 1564855 "overflowed integer argument" --
> basically eif_header.section_offsets[i] is from the data
> file and is a uint64_t, but we pass it to fseek() in
> read_eif_file. fseek() takes a signed offset, so if the
> section_offset in the file header is very large it will
> be unexpectedly treated as a negative number.
>
> CID 1564859 "untrusted value as argument" --
> in the "default" case for handling a section type
> we call g_malloc() with the uint64_t size from the header.
> If the file data is bogus we might try to allocate a
> huge amount of memory, which will cause g_malloc() to
> kill QEMU.
>
> thanks
> -- PMM
>
Dorjoy Chowdhury Nov. 5, 2024, 1:56 p.m. UTC | #3
On Tue, Nov 5, 2024 at 6:51 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> On Tue, Nov 5, 2024 at 12:44 PM Peter Maydell <peter.maydell@linaro.org> wrote:
> > Hi; Coverity raises a couple of potential issues with the
> > read_eif_file() function in this commit, which are both
> > "Coverity assumes the file we're reading is untrusted and is
> > unsure that we're correctly sanitizing data from it before use".
> > Could somebody who understands the use case here check whether
> > these need addressing?
>
> Both are reasonable to fix, even if the use case would not make them
> security sensitive. I'll prepare and send a patch.
>

Agree that it makes sense to fix. Thanks Paolo for looking into it. I
can review when the patch is ready.

BTW I see there is some formatting issue in the documentation of
nitro-enclave in the QEMU website:
https://www.qemu.org/docs/master/system/i386/nitro-enclave.html
I think it's a simple fix where we need to put two colons (::) in a
line before the QEMU commands lines. Maybe it would make sense to
include it in the patches as well.

Regards,
Dorjoy
Alexander Graf Nov. 5, 2024, 3:37 p.m. UTC | #4
Hi Dorjoy,


On 05.11.24 14:56, Dorjoy Chowdhury wrote:
> On Tue, Nov 5, 2024 at 6:51 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
>> On Tue, Nov 5, 2024 at 12:44 PM Peter Maydell <peter.maydell@linaro.org> wrote:
>>> Hi; Coverity raises a couple of potential issues with the
>>> read_eif_file() function in this commit, which are both
>>> "Coverity assumes the file we're reading is untrusted and is
>>> unsure that we're correctly sanitizing data from it before use".
>>> Could somebody who understands the use case here check whether
>>> these need addressing?
>> Both are reasonable to fix, even if the use case would not make them
>> security sensitive. I'll prepare and send a patch.
>>
> Agree that it makes sense to fix. Thanks Paolo for looking into it. I
> can review when the patch is ready.
>
> BTW I see there is some formatting issue in the documentation of
> nitro-enclave in the QEMU website:
> https://www.qemu.org/docs/master/system/i386/nitro-enclave.html
> I think it's a simple fix where we need to put two colons (::) in a
> line before the QEMU commands lines. Maybe it would make sense to
> include it in the patches as well.


Feel free to just send a patch to fix them on top of Paolo's pull 
request to the mailing list :).


Alex




Amazon Web Services Development Center Germany GmbH
Krausenstr. 38
10117 Berlin
Geschaeftsfuehrung: Christian Schlaeger, Jonathan Weiss
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
Dorjoy Chowdhury Nov. 5, 2024, 4:22 p.m. UTC | #5
Hi Alex,

On Tue, Nov 5, 2024 at 9:37 PM Alexander Graf <graf@amazon.com> wrote:
>
> Hi Dorjoy,
>
>
> On 05.11.24 14:56, Dorjoy Chowdhury wrote:
> > On Tue, Nov 5, 2024 at 6:51 PM Paolo Bonzini <pbonzini@redhat.com> wrote:
> >> On Tue, Nov 5, 2024 at 12:44 PM Peter Maydell <peter.maydell@linaro.org> wrote:
> >>> Hi; Coverity raises a couple of potential issues with the
> >>> read_eif_file() function in this commit, which are both
> >>> "Coverity assumes the file we're reading is untrusted and is
> >>> unsure that we're correctly sanitizing data from it before use".
> >>> Could somebody who understands the use case here check whether
> >>> these need addressing?
> >> Both are reasonable to fix, even if the use case would not make them
> >> security sensitive. I'll prepare and send a patch.
> >>
> > Agree that it makes sense to fix. Thanks Paolo for looking into it. I
> > can review when the patch is ready.
> >
> > BTW I see there is some formatting issue in the documentation of
> > nitro-enclave in the QEMU website:
> > https://www.qemu.org/docs/master/system/i386/nitro-enclave.html
> > I think it's a simple fix where we need to put two colons (::) in a
> > line before the QEMU commands lines. Maybe it would make sense to
> > include it in the patches as well.
>
>
> Feel free to just send a patch to fix them on top of Paolo's pull
> request to the mailing list :).
>

Yes, I can do that too with a patch on top of the master branch.

Regards,
Dorjoy
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 649bd5ab1a8..19b7d54a193 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1834,6 +1834,13 @@  F: hw/i386/microvm.c
 F: include/hw/i386/microvm.h
 F: pc-bios/bios-microvm.bin
 
+nitro-enclave
+M: Alexander Graf <graf@amazon.com>
+M: Dorjoy Chowdhury <dorjoychy111@gmail.com>
+S: Maintained
+F: hw/core/eif.c
+F: hw/core/eif.h
+
 Machine core
 M: Eduardo Habkost <eduardo@habkost.net>
 M: Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
diff --git a/meson.build b/meson.build
index 2739cc4964a..5bb519ea155 100644
--- a/meson.build
+++ b/meson.build
@@ -3137,6 +3137,7 @@  host_kconfig = \
   (have_ivshmem ? ['CONFIG_IVSHMEM=y'] : []) + \
   (opengl.found() ? ['CONFIG_OPENGL=y'] : []) + \
   (libcbor.found() ? ['CONFIG_LIBCBOR=y'] : []) + \
+  (gnutls.found() ? ['CONFIG_GNUTLS=y'] : []) + \
   (x11.found() ? ['CONFIG_X11=y'] : []) + \
   (fdt.found() ? ['CONFIG_FDT=y'] : []) + \
   (have_vhost_user ? ['CONFIG_VHOST_USER=y'] : []) + \
diff --git a/hw/core/eif.h b/hw/core/eif.h
new file mode 100644
index 00000000000..fed3cb55140
--- /dev/null
+++ b/hw/core/eif.h
@@ -0,0 +1,22 @@ 
+/*
+ * EIF (Enclave Image Format) related helpers
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#ifndef HW_CORE_EIF_H
+#define HW_CORE_EIF_H
+
+bool read_eif_file(const char *eif_path, const char *machine_initrd,
+                   char **kernel_path, char **initrd_path,
+                   char **kernel_cmdline, uint8_t *image_sha384,
+                   uint8_t *bootstrap_sha384, uint8_t *app_sha384,
+                   uint8_t *fingerprint_sha384, bool *signature_found,
+                   Error **errp);
+
+#endif
+
diff --git a/hw/core/eif.c b/hw/core/eif.c
new file mode 100644
index 00000000000..7f3b2edc9a7
--- /dev/null
+++ b/hw/core/eif.c
@@ -0,0 +1,719 @@ 
+/*
+ * EIF (Enclave Image Format) related helpers
+ *
+ * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version.  See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "qapi/error.h"
+#include "crypto/hash.h"
+#include "crypto/x509-utils.h"
+#include <zlib.h> /* for crc32 */
+#include <cbor.h>
+
+#include "hw/core/eif.h"
+
+#define MAX_SECTIONS 32
+
+/* members are ordered according to field order in .eif file */
+typedef struct EifHeader {
+    uint8_t  magic[4]; /* must be .eif in ascii i.e., [46, 101, 105, 102] */
+    uint16_t version;
+    uint16_t flags;
+    uint64_t default_memory;
+    uint64_t default_cpus;
+    uint16_t reserved;
+    uint16_t section_cnt;
+    uint64_t section_offsets[MAX_SECTIONS];
+    uint64_t section_sizes[MAX_SECTIONS];
+    uint32_t unused;
+    uint32_t eif_crc32;
+} QEMU_PACKED EifHeader;
+
+/* members are ordered according to field order in .eif file */
+typedef struct EifSectionHeader {
+    /*
+     * 0 = invalid, 1 = kernel, 2 = cmdline, 3 = ramdisk, 4 = signature,
+     * 5 = metadata
+     */
+    uint16_t section_type;
+    uint16_t flags;
+    uint64_t section_size;
+} QEMU_PACKED EifSectionHeader;
+
+enum EifSectionTypes {
+    EIF_SECTION_INVALID = 0,
+    EIF_SECTION_KERNEL = 1,
+    EIF_SECTION_CMDLINE = 2,
+    EIF_SECTION_RAMDISK = 3,
+    EIF_SECTION_SIGNATURE = 4,
+    EIF_SECTION_METADATA = 5,
+    EIF_SECTION_MAX = 6,
+};
+
+static const char *section_type_to_string(uint16_t type)
+{
+    const char *str;
+    switch (type) {
+    case EIF_SECTION_INVALID:
+        str = "invalid";
+        break;
+    case EIF_SECTION_KERNEL:
+        str = "kernel";
+        break;
+    case EIF_SECTION_CMDLINE:
+        str = "cmdline";
+        break;
+    case EIF_SECTION_RAMDISK:
+        str = "ramdisk";
+        break;
+    case EIF_SECTION_SIGNATURE:
+        str = "signature";
+        break;
+    case EIF_SECTION_METADATA:
+        str = "metadata";
+        break;
+    default:
+        str = "unknown";
+        break;
+    }
+
+    return str;
+}
+
+static bool read_eif_header(FILE *f, EifHeader *header, uint32_t *crc,
+                            Error **errp)
+{
+    size_t got;
+    size_t header_size = sizeof(*header);
+
+    got = fread(header, 1, header_size, f);
+    if (got != header_size) {
+        error_setg(errp, "Failed to read EIF header");
+        return false;
+    }
+
+    if (memcmp(header->magic, ".eif", 4) != 0) {
+        error_setg(errp, "Invalid EIF image. Magic mismatch.");
+        return false;
+    }
+
+    /* Exclude header->eif_crc32 field from CRC calculation */
+    *crc = crc32(*crc, (uint8_t *)header, header_size - 4);
+
+    header->version = be16_to_cpu(header->version);
+    header->flags = be16_to_cpu(header->flags);
+    header->default_memory = be64_to_cpu(header->default_memory);
+    header->default_cpus = be64_to_cpu(header->default_cpus);
+    header->reserved = be16_to_cpu(header->reserved);
+    header->section_cnt = be16_to_cpu(header->section_cnt);
+
+    for (int i = 0; i < MAX_SECTIONS; ++i) {
+        header->section_offsets[i] = be64_to_cpu(header->section_offsets[i]);
+    }
+
+    for (int i = 0; i < MAX_SECTIONS; ++i) {
+        header->section_sizes[i] = be64_to_cpu(header->section_sizes[i]);
+    }
+
+    header->unused = be32_to_cpu(header->unused);
+    header->eif_crc32 = be32_to_cpu(header->eif_crc32);
+    return true;
+}
+
+static bool read_eif_section_header(FILE *f, EifSectionHeader *section_header,
+                                    uint32_t *crc, Error **errp)
+{
+    size_t got;
+    size_t section_header_size = sizeof(*section_header);
+
+    got = fread(section_header, 1, section_header_size, f);
+    if (got != section_header_size) {
+        error_setg(errp, "Failed to read EIF section header");
+        return false;
+    }
+
+    *crc = crc32(*crc, (uint8_t *)section_header, section_header_size);
+
+    section_header->section_type = be16_to_cpu(section_header->section_type);
+    section_header->flags = be16_to_cpu(section_header->flags);
+    section_header->section_size = be64_to_cpu(section_header->section_size);
+    return true;
+}
+
+/*
+ * Upon success, the caller is responsible for unlinking and freeing *tmp_path.
+ */
+static bool get_tmp_file(const char *template, char **tmp_path, Error **errp)
+{
+    int tmp_fd;
+
+    *tmp_path = NULL;
+    tmp_fd = g_file_open_tmp(template, tmp_path, NULL);
+    if (tmp_fd < 0 || *tmp_path == NULL) {
+        error_setg(errp, "Failed to create temporary file for template %s",
+                   template);
+        return false;
+    }
+
+    close(tmp_fd);
+    return true;
+}
+
+static void safe_fclose(FILE *f)
+{
+    if (f) {
+        fclose(f);
+    }
+}
+
+static void safe_unlink(char *f)
+{
+    if (f) {
+        unlink(f);
+    }
+}
+
+/*
+ * Upon success, the caller is reponsible for unlinking and freeing *kernel_path
+ */
+static bool read_eif_kernel(FILE *f, uint64_t size, char **kernel_path,
+                            uint8_t *kernel, uint32_t *crc, Error **errp)
+{
+    size_t got;
+    FILE *tmp_file = NULL;
+
+    *kernel_path = NULL;
+    if (!get_tmp_file("eif-kernel-XXXXXX", kernel_path, errp)) {
+        goto cleanup;
+    }
+
+    tmp_file = fopen(*kernel_path, "wb");
+    if (tmp_file == NULL) {
+        error_setg_errno(errp, errno, "Failed to open temporary file %s",
+                         *kernel_path);
+        goto cleanup;
+    }
+
+    got = fread(kernel, 1, size, f);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to read EIF kernel section data");
+        goto cleanup;
+    }
+
+    got = fwrite(kernel, 1, size, tmp_file);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to write EIF kernel section data to temporary"
+                   " file");
+        goto cleanup;
+    }
+
+    *crc = crc32(*crc, kernel, size);
+    fclose(tmp_file);
+
+    return true;
+
+ cleanup:
+    safe_fclose(tmp_file);
+
+    safe_unlink(*kernel_path);
+    g_free(*kernel_path);
+    *kernel_path = NULL;
+
+    return false;
+}
+
+static bool read_eif_cmdline(FILE *f, uint64_t size, char *cmdline,
+                             uint32_t *crc, Error **errp)
+{
+    size_t got = fread(cmdline, 1, size, f);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to read EIF cmdline section data");
+        return false;
+    }
+
+    *crc = crc32(*crc, (uint8_t *)cmdline, size);
+    return true;
+}
+
+static bool read_eif_ramdisk(FILE *eif, FILE *initrd, uint64_t size,
+                             uint8_t *ramdisk, uint32_t *crc, Error **errp)
+{
+    size_t got;
+
+    got = fread(ramdisk, 1, size, eif);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to read EIF ramdisk section data");
+        return false;
+    }
+
+    got = fwrite(ramdisk, 1, size, initrd);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to write EIF ramdisk data to temporary file");
+        return false;
+    }
+
+    *crc = crc32(*crc, ramdisk, size);
+    return true;
+}
+
+static bool get_signature_fingerprint_sha384(FILE *eif, uint64_t size,
+                                             uint8_t *sha384,
+                                             uint32_t *crc,
+                                             Error **errp)
+{
+    size_t got;
+    g_autofree uint8_t *sig = NULL;
+    g_autofree uint8_t *cert = NULL;
+    cbor_item_t *item = NULL;
+    cbor_item_t *pcr0 = NULL;
+    size_t len;
+    size_t hash_len = QCRYPTO_HASH_DIGEST_LEN_SHA384;
+    struct cbor_pair *pair;
+    struct cbor_load_result result;
+    bool ret = false;
+
+    sig = g_malloc(size);
+    got = fread(sig, 1, size, eif);
+    if ((uint64_t) got != size) {
+        error_setg(errp, "Failed to read EIF signature section data");
+        goto cleanup;
+    }
+
+    *crc = crc32(*crc, sig, size);
+
+    item = cbor_load(sig, size, &result);
+    if (!item || result.error.code != CBOR_ERR_NONE) {
+        error_setg(errp, "Failed to load signature section data as CBOR");
+        goto cleanup;
+    }
+    if (!cbor_isa_array(item) || cbor_array_size(item) < 1) {
+        error_setg(errp, "Invalid signature CBOR");
+        goto cleanup;
+    }
+    pcr0 = cbor_array_get(item, 0);
+    if (!pcr0) {
+        error_setg(errp, "Failed to get PCR0 signature");
+        goto cleanup;
+    }
+    if (!cbor_isa_map(pcr0) || cbor_map_size(pcr0) != 2) {
+        error_setg(errp, "Invalid signature CBOR");
+        goto cleanup;
+    }
+    pair = cbor_map_handle(pcr0);
+    if (!cbor_isa_string(pair->key) || cbor_string_length(pair->key) != 19 ||
+        memcmp(cbor_string_handle(pair->key), "signing_certificate", 19) != 0) {
+        error_setg(errp, "Invalid signautre CBOR");
+        goto cleanup;
+    }
+    if (!cbor_isa_array(pair->value)) {
+        error_setg(errp, "Invalid signature CBOR");
+        goto cleanup;
+    }
+    len = cbor_array_size(pair->value);
+    if (len == 0) {
+        error_setg(errp, "Invalid signature CBOR");
+        goto cleanup;
+    }
+    cert = g_malloc(len);
+    for (int i = 0; i < len; ++i) {
+        cbor_item_t *tmp = cbor_array_get(pair->value, i);
+        if (!tmp) {
+            error_setg(errp, "Invalid signature CBOR");
+            goto cleanup;
+        }
+        if (!cbor_isa_uint(tmp) || cbor_int_get_width(tmp) != CBOR_INT_8) {
+            cbor_decref(&tmp);
+            error_setg(errp, "Invalid signature CBOR");
+            goto cleanup;
+        }
+        cert[i] = cbor_get_uint8(tmp);
+        cbor_decref(&tmp);
+    }
+
+    if (qcrypto_get_x509_cert_fingerprint(cert, len, QCRYPTO_HASH_ALGO_SHA384,
+                                          sha384, &hash_len, errp)) {
+        goto cleanup;
+    }
+
+    ret = true;
+
+ cleanup:
+    if (pcr0) {
+        cbor_decref(&pcr0);
+    }
+    if (item) {
+        cbor_decref(&item);
+    }
+    return ret;
+}
+
+/* Expects file to have offset 0 before this function is called */
+static long get_file_size(FILE *f, Error **errp)
+{
+    long size;
+
+    if (fseek(f, 0, SEEK_END) != 0) {
+        error_setg_errno(errp, errno, "Failed to seek to the end of file");
+        return -1;
+    }
+
+    size = ftell(f);
+    if (size == -1) {
+        error_setg_errno(errp, errno, "Failed to get offset");
+        return -1;
+    }
+
+    if (fseek(f, 0, SEEK_SET) != 0) {
+        error_setg_errno(errp, errno, "Failed to seek back to the start");
+        return -1;
+    }
+
+    return size;
+}
+
+static bool get_SHA384_digest(GList *list, uint8_t *digest, Error **errp)
+{
+    size_t digest_len = QCRYPTO_HASH_DIGEST_LEN_SHA384;
+    size_t list_len = g_list_length(list);
+    struct iovec *iovec_list = g_new0(struct iovec, list_len);
+    bool ret = true;
+    GList *l;
+    int i;
+
+    for (i = 0, l = list; l != NULL; l = l->next, i++) {
+        iovec_list[i] = *(struct iovec *) l->data;
+    }
+
+    if (qcrypto_hash_bytesv(QCRYPTO_HASH_ALGO_SHA384, iovec_list, list_len,
+                            &digest, &digest_len, errp) < 0) {
+        ret = false;
+    }
+
+    g_free(iovec_list);
+    return ret;
+}
+
+static void free_iovec(struct iovec *iov)
+{
+    if (iov) {
+        g_free(iov->iov_base);
+        g_free(iov);
+    }
+}
+
+/*
+ * Upon success, the caller is reponsible for unlinking and freeing
+ * *kernel_path, *initrd_path and freeing *cmdline.
+ */
+bool read_eif_file(const char *eif_path, const char *machine_initrd,
+                   char **kernel_path, char **initrd_path, char **cmdline,
+                   uint8_t *image_sha384, uint8_t *bootstrap_sha384,
+                   uint8_t *app_sha384, uint8_t *fingerprint_sha384,
+                   bool *signature_found, Error **errp)
+{
+    FILE *f = NULL;
+    FILE *machine_initrd_f = NULL;
+    FILE *initrd_path_f = NULL;
+    long machine_initrd_size;
+    uint32_t crc = 0;
+    EifHeader eif_header;
+    bool seen_sections[EIF_SECTION_MAX] = {false};
+    /* kernel + ramdisks + cmdline sha384 hash */
+    GList *iov_PCR0 = NULL;
+    /* kernel + boot ramdisk + cmdline sha384 hash */
+    GList *iov_PCR1 = NULL;
+    /* application ramdisk(s) hash */
+    GList *iov_PCR2 = NULL;
+    uint8_t *ptr = NULL;
+    struct iovec *iov_ptr = NULL;
+
+    *signature_found = false;
+    *kernel_path = *initrd_path = *cmdline = NULL;
+
+    f = fopen(eif_path, "rb");
+    if (f == NULL) {
+        error_setg_errno(errp, errno, "Failed to open %s", eif_path);
+        goto cleanup;
+    }
+
+    if (!read_eif_header(f, &eif_header, &crc, errp)) {
+        goto cleanup;
+    }
+
+    if (eif_header.version < 4) {
+        error_setg(errp, "Expected EIF version 4 or greater");
+        goto cleanup;
+    }
+
+    if (eif_header.flags != 0) {
+        error_setg(errp, "Expected EIF flags to be 0");
+        goto cleanup;
+    }
+
+    if (eif_header.section_cnt > MAX_SECTIONS) {
+        error_setg(errp, "EIF header section count must not be greater than "
+                   "%d but found %d", MAX_SECTIONS, eif_header.section_cnt);
+        goto cleanup;
+    }
+
+    for (int i = 0; i < eif_header.section_cnt; ++i) {
+        EifSectionHeader hdr;
+        uint16_t section_type;
+
+        if (fseek(f, eif_header.section_offsets[i], SEEK_SET) != 0) {
+            error_setg_errno(errp, errno, "Failed to offset to %" PRIu64 " in EIF file",
+                             eif_header.section_offsets[i]);
+            goto cleanup;
+        }
+
+        if (!read_eif_section_header(f, &hdr, &crc, errp)) {
+            goto cleanup;
+        }
+
+        if (hdr.flags != 0) {
+            error_setg(errp, "Expected EIF section header flags to be 0");
+            goto cleanup;
+        }
+
+        if (eif_header.section_sizes[i] != hdr.section_size) {
+            error_setg(errp, "EIF section size mismatch between header and "
+                       "section header: header %" PRIu64 ", section header %" PRIu64,
+                       eif_header.section_sizes[i],
+                       hdr.section_size);
+            goto cleanup;
+        }
+
+        section_type = hdr.section_type;
+
+        switch (section_type) {
+        case EIF_SECTION_KERNEL:
+            if (seen_sections[EIF_SECTION_KERNEL]) {
+                error_setg(errp, "Invalid EIF image. More than 1 kernel "
+                           "section");
+                goto cleanup;
+            }
+
+            ptr = g_malloc(hdr.section_size);
+
+            iov_ptr = g_malloc(sizeof(struct iovec));
+            iov_ptr->iov_base = ptr;
+            iov_ptr->iov_len = hdr.section_size;
+
+            iov_PCR0 = g_list_append(iov_PCR0, iov_ptr);
+            iov_PCR1 = g_list_append(iov_PCR1, iov_ptr);
+
+            if (!read_eif_kernel(f, hdr.section_size, kernel_path, ptr, &crc,
+                                 errp)) {
+                goto cleanup;
+            }
+
+            break;
+        case EIF_SECTION_CMDLINE:
+        {
+            uint64_t size;
+            uint8_t *cmdline_copy;
+            if (seen_sections[EIF_SECTION_CMDLINE]) {
+                error_setg(errp, "Invalid EIF image. More than 1 cmdline "
+                           "section");
+                goto cleanup;
+            }
+            size = hdr.section_size;
+            *cmdline = g_malloc(size + 1);
+            if (!read_eif_cmdline(f, size, *cmdline, &crc, errp)) {
+                goto cleanup;
+            }
+            (*cmdline)[size] = '\0';
+
+            /*
+             * We make a copy of '*cmdline' for putting it in iovecs so that
+             * we can easily free all the iovec entries later as we cannot
+             * free '*cmdline' which is used by the caller.
+             */
+            cmdline_copy = g_memdup2(*cmdline, size);
+
+            iov_ptr = g_malloc(sizeof(struct iovec));
+            iov_ptr->iov_base = cmdline_copy;
+            iov_ptr->iov_len = size;
+
+            iov_PCR0 = g_list_append(iov_PCR0, iov_ptr);
+            iov_PCR1 = g_list_append(iov_PCR1, iov_ptr);
+            break;
+        }
+        case EIF_SECTION_RAMDISK:
+        {
+            if (!seen_sections[EIF_SECTION_RAMDISK]) {
+                /*
+                 * If this is the first time we are seeing a ramdisk section,
+                 * we need to create the initrd temporary file.
+                 */
+                if (!get_tmp_file("eif-initrd-XXXXXX", initrd_path, errp)) {
+                    goto cleanup;
+                }
+                initrd_path_f = fopen(*initrd_path, "wb");
+                if (initrd_path_f == NULL) {
+                    error_setg_errno(errp, errno, "Failed to open file %s",
+                                     *initrd_path);
+                    goto cleanup;
+                }
+            }
+
+            ptr = g_malloc(hdr.section_size);
+
+            iov_ptr = g_malloc(sizeof(struct iovec));
+            iov_ptr->iov_base = ptr;
+            iov_ptr->iov_len = hdr.section_size;
+
+            iov_PCR0 = g_list_append(iov_PCR0, iov_ptr);
+            /*
+             * If it's the first ramdisk, we need to hash it into bootstrap
+             * i.e., iov_PCR1, otherwise we need to hash it into app i.e.,
+             * iov_PCR2.
+             */
+            if (!seen_sections[EIF_SECTION_RAMDISK]) {
+                iov_PCR1 = g_list_append(iov_PCR1, iov_ptr);
+            } else {
+                iov_PCR2 = g_list_append(iov_PCR2, iov_ptr);
+            }
+
+            if (!read_eif_ramdisk(f, initrd_path_f, hdr.section_size, ptr,
+                                  &crc, errp)) {
+                goto cleanup;
+            }
+
+            break;
+        }
+        case EIF_SECTION_SIGNATURE:
+            *signature_found = true;
+            if (!get_signature_fingerprint_sha384(f, hdr.section_size,
+                                                  fingerprint_sha384, &crc,
+                                                  errp)) {
+                goto cleanup;
+            }
+            break;
+        default:
+            /* other sections including invalid or unknown sections */
+        {
+            uint8_t *buf;
+            size_t got;
+            uint64_t size = hdr.section_size;
+            buf = g_malloc(size);
+            got = fread(buf, 1, size, f);
+            if ((uint64_t) got != size) {
+                g_free(buf);
+                error_setg(errp, "Failed to read EIF %s section data",
+                           section_type_to_string(section_type));
+                goto cleanup;
+            }
+            crc = crc32(crc, buf, size);
+            g_free(buf);
+            break;
+        }
+        }
+
+        if (section_type < EIF_SECTION_MAX) {
+            seen_sections[section_type] = true;
+        }
+    }
+
+    if (!seen_sections[EIF_SECTION_KERNEL]) {
+        error_setg(errp, "Invalid EIF image. No kernel section.");
+        goto cleanup;
+    }
+    if (!seen_sections[EIF_SECTION_CMDLINE]) {
+        error_setg(errp, "Invalid EIF image. No cmdline section.");
+        goto cleanup;
+    }
+    if (!seen_sections[EIF_SECTION_RAMDISK]) {
+        error_setg(errp, "Invalid EIF image. No ramdisk section.");
+        goto cleanup;
+    }
+
+    if (eif_header.eif_crc32 != crc) {
+        error_setg(errp, "CRC mismatch. Expected %u but header has %u.",
+                   crc, eif_header.eif_crc32);
+        goto cleanup;
+    }
+
+    /*
+     * Let's append the initrd file from "-initrd" option if any. Although
+     * we pass the crc pointer to read_eif_ramdisk, it is not useful anymore.
+     * We have already done the crc mismatch check above this code.
+     */
+    if (machine_initrd) {
+        machine_initrd_f = fopen(machine_initrd, "rb");
+        if (machine_initrd_f == NULL) {
+            error_setg_errno(errp, errno, "Failed to open initrd file %s",
+                             machine_initrd);
+            goto cleanup;
+        }
+
+        machine_initrd_size = get_file_size(machine_initrd_f, errp);
+        if (machine_initrd_size == -1) {
+            goto cleanup;
+        }
+
+        ptr = g_malloc(machine_initrd_size);
+
+        iov_ptr = g_malloc(sizeof(struct iovec));
+        iov_ptr->iov_base = ptr;
+        iov_ptr->iov_len = machine_initrd_size;
+
+        iov_PCR0 = g_list_append(iov_PCR0, iov_ptr);
+        iov_PCR2 = g_list_append(iov_PCR2, iov_ptr);
+
+        if (!read_eif_ramdisk(machine_initrd_f, initrd_path_f,
+                              machine_initrd_size, ptr, &crc, errp)) {
+            goto cleanup;
+        }
+    }
+
+    if (!get_SHA384_digest(iov_PCR0, image_sha384, errp)) {
+        goto cleanup;
+    }
+    if (!get_SHA384_digest(iov_PCR1, bootstrap_sha384, errp)) {
+        goto cleanup;
+    }
+    if (!get_SHA384_digest(iov_PCR2, app_sha384, errp)) {
+        goto cleanup;
+    }
+
+    /*
+     * We only need to free iov_PCR0 entries because iov_PCR1 and
+     * iov_PCR2 iovec entries are subsets of iov_PCR0 iovec entries.
+     */
+    g_list_free_full(iov_PCR0, (GDestroyNotify) free_iovec);
+    g_list_free(iov_PCR1);
+    g_list_free(iov_PCR2);
+    fclose(f);
+    fclose(initrd_path_f);
+    safe_fclose(machine_initrd_f);
+    return true;
+
+ cleanup:
+    g_list_free_full(iov_PCR0, (GDestroyNotify) free_iovec);
+    g_list_free(iov_PCR1);
+    g_list_free(iov_PCR2);
+
+    safe_fclose(f);
+    safe_fclose(initrd_path_f);
+    safe_fclose(machine_initrd_f);
+
+    safe_unlink(*kernel_path);
+    g_free(*kernel_path);
+    *kernel_path = NULL;
+
+    safe_unlink(*initrd_path);
+    g_free(*initrd_path);
+    *initrd_path = NULL;
+
+    g_free(*cmdline);
+    *cmdline = NULL;
+
+    return false;
+}
diff --git a/Kconfig.host b/Kconfig.host
index ccd84be60f6..842cbe0d6c5 100644
--- a/Kconfig.host
+++ b/Kconfig.host
@@ -8,6 +8,9 @@  config LINUX
 config LIBCBOR
     bool
 
+config GNUTLS
+    bool
+
 config OPENGL
     bool
 
diff --git a/hw/core/Kconfig b/hw/core/Kconfig
index 24411f59306..d1bdf765ee8 100644
--- a/hw/core/Kconfig
+++ b/hw/core/Kconfig
@@ -34,3 +34,7 @@  config REGISTER
 
 config SPLIT_IRQ
     bool
+
+config EIF
+    bool
+    depends on LIBCBOR && GNUTLS
diff --git a/hw/core/meson.build b/hw/core/meson.build
index a3d9bab9f42..9fd0b5aaa5e 100644
--- a/hw/core/meson.build
+++ b/hw/core/meson.build
@@ -24,6 +24,7 @@  system_ss.add(when: 'CONFIG_REGISTER', if_true: files('register.c'))
 system_ss.add(when: 'CONFIG_SPLIT_IRQ', if_true: files('split-irq.c'))
 system_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('stream.c'))
 system_ss.add(when: 'CONFIG_PLATFORM_BUS', if_true: files('sysbus-fdt.c'))
+system_ss.add(when: 'CONFIG_EIF', if_true: [files('eif.c'), zlib, libcbor, gnutls])
 
 system_ss.add(files(
   'cpu-sysemu.c',