Message ID | 20240229063726.610065-38-xiaoyao.li@intel.com |
---|---|
State | New |
Headers | show |
Series | QEMU Guest memfd + QEMU TDX support | expand |
On 2/29/2024 2:36 PM, Xiaoyao Li wrote: > From: Isaku Yamahata <isaku.yamahata@intel.com> > > TDX VM needs to boot with its specialized firmware, Trusted Domain > Virtual Firmware (TDVF). QEMU needs to parse TDVF and map it in TD > guest memory prior to running the TDX VM. > > A TDVF Metadata in TDVF image describes the structure of firmware. > QEMU refers to it to setup memory for TDVF. Introduce function > tdvf_parse_metadata() to parse the metadata from TDVF image and store > the info of each TDVF section. > > TDX metadata is located by a TDX metadata offset block, which is a > GUID-ed structure. The data portion of the GUID structure contains > only an 4-byte field that is the offset of TDX metadata to the end > of firmware file. > > Select X86_FW_OVMF when TDX is enable to leverage existing functions > to parse and search OVMF's GUID-ed structures. > > Signed-off-by: Isaku Yamahata <isaku.yamahata@intel.com> > Co-developed-by: Xiaoyao Li <xiaoyao.li@intel.com> > Signed-off-by: Xiaoyao Li <xiaoyao.li@intel.com> > Acked-by: Gerd Hoffmann <kraxel@redhat.com> > > --- > Changes in v1: > - rename tdvf_parse_section_entry() to > tdvf_parse_and_check_section_entry() > Changes in RFC v4: > - rename TDX_METADATA_GUID to TDX_METADATA_OFFSET_GUID > --- > hw/i386/Kconfig | 1 + > hw/i386/meson.build | 1 + > hw/i386/tdvf.c | 199 +++++++++++++++++++++++++++++++++++++++++ > include/hw/i386/tdvf.h | 51 +++++++++++ > 4 files changed, 252 insertions(+) > create mode 100644 hw/i386/tdvf.c > create mode 100644 include/hw/i386/tdvf.h > > diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig > index c0ccf50ac3ef..4e6c8905f077 100644 > --- a/hw/i386/Kconfig > +++ b/hw/i386/Kconfig > @@ -12,6 +12,7 @@ config SGX > > config TDX > bool > + select X86_FW_OVMF > depends on KVM > > config PC > diff --git a/hw/i386/meson.build b/hw/i386/meson.build > index b9c1ca39cb05..f09441c1ea54 100644 > --- a/hw/i386/meson.build > +++ b/hw/i386/meson.build > @@ -28,6 +28,7 @@ i386_ss.add(when: 'CONFIG_PC', if_true: files( > 'port92.c')) > i386_ss.add(when: 'CONFIG_X86_FW_OVMF', if_true: files('pc_sysfw_ovmf.c'), > if_false: files('pc_sysfw_ovmf-stubs.c')) > +i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c')) > > subdir('kvm') > subdir('xen') > diff --git a/hw/i386/tdvf.c b/hw/i386/tdvf.c > new file mode 100644 > index 000000000000..ff51f40088f0 > --- /dev/null > +++ b/hw/i386/tdvf.c > @@ -0,0 +1,199 @@ > +/* > + * SPDX-License-Identifier: GPL-2.0-or-later > + > + * Copyright (c) 2020 Intel Corporation > + * Author: Isaku Yamahata <isaku.yamahata at gmail.com> > + * <isaku.yamahata at intel.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + > + * You should have received a copy of the GNU General Public License along > + * with this program; if not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/error-report.h" > + > +#include "hw/i386/pc.h" > +#include "hw/i386/tdvf.h" > +#include "sysemu/kvm.h" > + > +#define TDX_METADATA_OFFSET_GUID "e47a6535-984a-4798-865e-4685a7bf8ec2" > +#define TDX_METADATA_VERSION 1 > +#define TDVF_SIGNATURE 0x46564454 /* TDVF as little endian */ > + > +typedef struct { > + uint32_t DataOffset; > + uint32_t RawDataSize; > + uint64_t MemoryAddress; > + uint64_t MemoryDataSize; > + uint32_t Type; > + uint32_t Attributes; > +} TdvfSectionEntry; > + > +typedef struct { > + uint32_t Signature; > + uint32_t Length; > + uint32_t Version; > + uint32_t NumberOfSectionEntries; > + TdvfSectionEntry SectionEntries[]; > +} TdvfMetadata; > + > +struct tdx_metadata_offset { > + uint32_t offset; > +}; > + > +static TdvfMetadata *tdvf_get_metadata(void *flash_ptr, int size) > +{ > + TdvfMetadata *metadata; > + uint32_t offset = 0; > + uint8_t *data; > + > + if ((uint32_t) size != size) { > + return NULL; > + } > + > + if (pc_system_ovmf_table_find(TDX_METADATA_OFFSET_GUID, &data, NULL)) { > + offset = size - le32_to_cpu(((struct tdx_metadata_offset *)data)->offset); > + > + if (offset + sizeof(*metadata) > size) { > + return NULL; > + } > + } else { > + error_report("Cannot find TDX_METADATA_OFFSET_GUID"); > + return NULL; > + } > + > + metadata = flash_ptr + offset; > + > + /* Finally, verify the signature to determine if this is a TDVF image. */ > + metadata->Signature = le32_to_cpu(metadata->Signature); > + if (metadata->Signature != TDVF_SIGNATURE) { > + error_report("Invalid TDVF signature in metadata!"); > + return NULL; > + } > + > + /* Sanity check that the TDVF doesn't overlap its own metadata. */ > + metadata->Length = le32_to_cpu(metadata->Length); > + if (offset + metadata->Length > size) { > + return NULL; > + } > + > + /* Only version 1 is supported/defined. */ > + metadata->Version = le32_to_cpu(metadata->Version); > + if (metadata->Version != TDX_METADATA_VERSION) { > + return NULL; > + } > + > + return metadata; > +} > + > +static int tdvf_parse_and_check_section_entry(const TdvfSectionEntry *src, > + TdxFirmwareEntry *entry) > +{ > + entry->data_offset = le32_to_cpu(src->DataOffset); > + entry->data_len = le32_to_cpu(src->RawDataSize); > + entry->address = le64_to_cpu(src->MemoryAddress); > + entry->size = le64_to_cpu(src->MemoryDataSize); > + entry->type = le32_to_cpu(src->Type); > + entry->attributes = le32_to_cpu(src->Attributes); > + > + /* sanity check */ > + if (entry->size < entry->data_len) { > + error_report("Broken metadata RawDataSize 0x%x MemoryDataSize 0x%lx", > + entry->data_len, entry->size); > + return -1; > + } > + if (!QEMU_IS_ALIGNED(entry->address, TARGET_PAGE_SIZE)) { > + error_report("MemoryAddress 0x%lx not page aligned", entry->address); > + return -1; > + } > + if (!QEMU_IS_ALIGNED(entry->size, TARGET_PAGE_SIZE)) { > + error_report("MemoryDataSize 0x%lx not page aligned", entry->size); > + return -1; > + } > + > + switch (entry->type) { > + case TDVF_SECTION_TYPE_BFV: > + case TDVF_SECTION_TYPE_CFV: > + /* The sections that must be copied from firmware image to TD memory */ > + if (entry->data_len == 0) { > + error_report("%d section with RawDataSize == 0", entry->type); > + return -1; > + } > + break; > + case TDVF_SECTION_TYPE_TD_HOB: > + case TDVF_SECTION_TYPE_TEMP_MEM: > + /* The sections that no need to be copied from firmware image */ > + if (entry->data_len != 0) { > + error_report("%d section with RawDataSize 0x%x != 0", > + entry->type, entry->data_len); > + return -1; > + } > + break; > + default: > + error_report("TDVF contains unsupported section type %d", entry->type); > + return -1; > + } > + > + return 0; > +} > + > +int tdvf_parse_metadata(TdxFirmware *fw, void *flash_ptr, int size) > +{ > + TdvfSectionEntry *sections; > + TdvfMetadata *metadata; > + ssize_t entries_size; > + uint32_t len, i; > + > + metadata = tdvf_get_metadata(flash_ptr, size); > + if (!metadata) { > + return -EINVAL; > + } > + > + //load and parse metadata entries > + fw->nr_entries = le32_to_cpu(metadata->NumberOfSectionEntries); > + if (fw->nr_entries < 2) { > + error_report("Invalid number of fw entries (%u) in TDVF", fw->nr_entries); > + return -EINVAL; > + } > + > + len = le32_to_cpu(metadata->Length); metadata->Length is already CPU endian, any reason to convert again? > + entries_size = fw->nr_entries * sizeof(TdvfSectionEntry); > + if (len != sizeof(*metadata) + entries_size) { > + error_report("TDVF metadata len (0x%x) mismatch, expected (0x%x)", > + len, (uint32_t)(sizeof(*metadata) + entries_size)); > + return -EINVAL; > + } > + > + fw->entries = g_new(TdxFirmwareEntry, fw->nr_entries); > + sections = g_new(TdvfSectionEntry, fw->nr_entries); > + > + if (!memcpy(sections, (void *)metadata + sizeof(*metadata), entries_size)) { > + error_report("Failed to read TDVF section entries"); > + goto err; > + } > + > + for (i = 0; i < fw->nr_entries; i++) { > + if (tdvf_parse_and_check_section_entry(§ions[i], &fw->entries[i])) { > + goto err; > + } > + } > + g_free(sections); > + > + return 0; > + > +err: > + g_free(sections); This can be g_autofree. Thanks Zhenzhong
diff --git a/hw/i386/Kconfig b/hw/i386/Kconfig index c0ccf50ac3ef..4e6c8905f077 100644 --- a/hw/i386/Kconfig +++ b/hw/i386/Kconfig @@ -12,6 +12,7 @@ config SGX config TDX bool + select X86_FW_OVMF depends on KVM config PC diff --git a/hw/i386/meson.build b/hw/i386/meson.build index b9c1ca39cb05..f09441c1ea54 100644 --- a/hw/i386/meson.build +++ b/hw/i386/meson.build @@ -28,6 +28,7 @@ i386_ss.add(when: 'CONFIG_PC', if_true: files( 'port92.c')) i386_ss.add(when: 'CONFIG_X86_FW_OVMF', if_true: files('pc_sysfw_ovmf.c'), if_false: files('pc_sysfw_ovmf-stubs.c')) +i386_ss.add(when: 'CONFIG_TDX', if_true: files('tdvf.c')) subdir('kvm') subdir('xen') diff --git a/hw/i386/tdvf.c b/hw/i386/tdvf.c new file mode 100644 index 000000000000..ff51f40088f0 --- /dev/null +++ b/hw/i386/tdvf.c @@ -0,0 +1,199 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + + * Copyright (c) 2020 Intel Corporation + * Author: Isaku Yamahata <isaku.yamahata at gmail.com> + * <isaku.yamahata at intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" + +#include "hw/i386/pc.h" +#include "hw/i386/tdvf.h" +#include "sysemu/kvm.h" + +#define TDX_METADATA_OFFSET_GUID "e47a6535-984a-4798-865e-4685a7bf8ec2" +#define TDX_METADATA_VERSION 1 +#define TDVF_SIGNATURE 0x46564454 /* TDVF as little endian */ + +typedef struct { + uint32_t DataOffset; + uint32_t RawDataSize; + uint64_t MemoryAddress; + uint64_t MemoryDataSize; + uint32_t Type; + uint32_t Attributes; +} TdvfSectionEntry; + +typedef struct { + uint32_t Signature; + uint32_t Length; + uint32_t Version; + uint32_t NumberOfSectionEntries; + TdvfSectionEntry SectionEntries[]; +} TdvfMetadata; + +struct tdx_metadata_offset { + uint32_t offset; +}; + +static TdvfMetadata *tdvf_get_metadata(void *flash_ptr, int size) +{ + TdvfMetadata *metadata; + uint32_t offset = 0; + uint8_t *data; + + if ((uint32_t) size != size) { + return NULL; + } + + if (pc_system_ovmf_table_find(TDX_METADATA_OFFSET_GUID, &data, NULL)) { + offset = size - le32_to_cpu(((struct tdx_metadata_offset *)data)->offset); + + if (offset + sizeof(*metadata) > size) { + return NULL; + } + } else { + error_report("Cannot find TDX_METADATA_OFFSET_GUID"); + return NULL; + } + + metadata = flash_ptr + offset; + + /* Finally, verify the signature to determine if this is a TDVF image. */ + metadata->Signature = le32_to_cpu(metadata->Signature); + if (metadata->Signature != TDVF_SIGNATURE) { + error_report("Invalid TDVF signature in metadata!"); + return NULL; + } + + /* Sanity check that the TDVF doesn't overlap its own metadata. */ + metadata->Length = le32_to_cpu(metadata->Length); + if (offset + metadata->Length > size) { + return NULL; + } + + /* Only version 1 is supported/defined. */ + metadata->Version = le32_to_cpu(metadata->Version); + if (metadata->Version != TDX_METADATA_VERSION) { + return NULL; + } + + return metadata; +} + +static int tdvf_parse_and_check_section_entry(const TdvfSectionEntry *src, + TdxFirmwareEntry *entry) +{ + entry->data_offset = le32_to_cpu(src->DataOffset); + entry->data_len = le32_to_cpu(src->RawDataSize); + entry->address = le64_to_cpu(src->MemoryAddress); + entry->size = le64_to_cpu(src->MemoryDataSize); + entry->type = le32_to_cpu(src->Type); + entry->attributes = le32_to_cpu(src->Attributes); + + /* sanity check */ + if (entry->size < entry->data_len) { + error_report("Broken metadata RawDataSize 0x%x MemoryDataSize 0x%lx", + entry->data_len, entry->size); + return -1; + } + if (!QEMU_IS_ALIGNED(entry->address, TARGET_PAGE_SIZE)) { + error_report("MemoryAddress 0x%lx not page aligned", entry->address); + return -1; + } + if (!QEMU_IS_ALIGNED(entry->size, TARGET_PAGE_SIZE)) { + error_report("MemoryDataSize 0x%lx not page aligned", entry->size); + return -1; + } + + switch (entry->type) { + case TDVF_SECTION_TYPE_BFV: + case TDVF_SECTION_TYPE_CFV: + /* The sections that must be copied from firmware image to TD memory */ + if (entry->data_len == 0) { + error_report("%d section with RawDataSize == 0", entry->type); + return -1; + } + break; + case TDVF_SECTION_TYPE_TD_HOB: + case TDVF_SECTION_TYPE_TEMP_MEM: + /* The sections that no need to be copied from firmware image */ + if (entry->data_len != 0) { + error_report("%d section with RawDataSize 0x%x != 0", + entry->type, entry->data_len); + return -1; + } + break; + default: + error_report("TDVF contains unsupported section type %d", entry->type); + return -1; + } + + return 0; +} + +int tdvf_parse_metadata(TdxFirmware *fw, void *flash_ptr, int size) +{ + TdvfSectionEntry *sections; + TdvfMetadata *metadata; + ssize_t entries_size; + uint32_t len, i; + + metadata = tdvf_get_metadata(flash_ptr, size); + if (!metadata) { + return -EINVAL; + } + + //load and parse metadata entries + fw->nr_entries = le32_to_cpu(metadata->NumberOfSectionEntries); + if (fw->nr_entries < 2) { + error_report("Invalid number of fw entries (%u) in TDVF", fw->nr_entries); + return -EINVAL; + } + + len = le32_to_cpu(metadata->Length); + entries_size = fw->nr_entries * sizeof(TdvfSectionEntry); + if (len != sizeof(*metadata) + entries_size) { + error_report("TDVF metadata len (0x%x) mismatch, expected (0x%x)", + len, (uint32_t)(sizeof(*metadata) + entries_size)); + return -EINVAL; + } + + fw->entries = g_new(TdxFirmwareEntry, fw->nr_entries); + sections = g_new(TdvfSectionEntry, fw->nr_entries); + + if (!memcpy(sections, (void *)metadata + sizeof(*metadata), entries_size)) { + error_report("Failed to read TDVF section entries"); + goto err; + } + + for (i = 0; i < fw->nr_entries; i++) { + if (tdvf_parse_and_check_section_entry(§ions[i], &fw->entries[i])) { + goto err; + } + } + g_free(sections); + + return 0; + +err: + g_free(sections); + fw->entries = 0; + g_free(fw->entries); + return -EINVAL; +} diff --git a/include/hw/i386/tdvf.h b/include/hw/i386/tdvf.h new file mode 100644 index 000000000000..593341eb2e93 --- /dev/null +++ b/include/hw/i386/tdvf.h @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + + * Copyright (c) 2020 Intel Corporation + * Author: Isaku Yamahata <isaku.yamahata at gmail.com> + * <isaku.yamahata at intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef HW_I386_TDVF_H +#define HW_I386_TDVF_H + +#include "qemu/osdep.h" + +#define TDVF_SECTION_TYPE_BFV 0 +#define TDVF_SECTION_TYPE_CFV 1 +#define TDVF_SECTION_TYPE_TD_HOB 2 +#define TDVF_SECTION_TYPE_TEMP_MEM 3 + +#define TDVF_SECTION_ATTRIBUTES_MR_EXTEND (1U << 0) +#define TDVF_SECTION_ATTRIBUTES_PAGE_AUG (1U << 1) + +typedef struct TdxFirmwareEntry { + uint32_t data_offset; + uint32_t data_len; + uint64_t address; + uint64_t size; + uint32_t type; + uint32_t attributes; +} TdxFirmwareEntry; + +typedef struct TdxFirmware { + uint32_t nr_entries; + TdxFirmwareEntry *entries; +} TdxFirmware; + +int tdvf_parse_metadata(TdxFirmware *fw, void *flash_ptr, int size); + +#endif /* HW_I386_TDVF_H */