| /* |
| * QEMU IGVM configuration backend for guests |
| * |
| * Copyright (C) 2023-2024 SUSE |
| * |
| * Authors: |
| * Roy Hopkins <roy.hopkins@randomman.co.uk> |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| #include "qemu/osdep.h" |
| |
| #include "igvm.h" |
| #include "qapi/error.h" |
| #include "system/memory.h" |
| #include "system/address-spaces.h" |
| #include "hw/core/cpu.h" |
| |
| #include <igvm/igvm.h> |
| #include <igvm/igvm_defs.h> |
| |
| typedef struct QIgvmParameterData { |
| QTAILQ_ENTRY(QIgvmParameterData) next; |
| uint8_t *data; |
| uint32_t size; |
| uint32_t index; |
| } QIgvmParameterData; |
| |
| /* |
| * Some directives are specific to particular confidential computing platforms. |
| * Define required types for each of those platforms here. |
| */ |
| |
| /* SEV/SEV-ES/SEV-SNP */ |
| |
| /* |
| * These structures are defined in "SEV Secure Nested Paging Firmware ABI |
| * Specification" Rev 1.58, section 8.18. |
| */ |
| struct QEMU_PACKED sev_id_block { |
| uint8_t ld[48]; |
| uint8_t family_id[16]; |
| uint8_t image_id[16]; |
| uint32_t version; |
| uint32_t guest_svn; |
| uint64_t policy; |
| }; |
| |
| struct QEMU_PACKED sev_id_authentication { |
| uint32_t id_key_alg; |
| uint32_t auth_key_algo; |
| uint8_t reserved[56]; |
| uint8_t id_block_sig[512]; |
| uint8_t id_key[1028]; |
| uint8_t reserved2[60]; |
| uint8_t id_key_sig[512]; |
| uint8_t author_key[1028]; |
| uint8_t reserved3[892]; |
| }; |
| |
| #define IGVM_SEV_ID_BLOCK_VERSION 1 |
| |
| /* |
| * QIgvm contains the information required during processing |
| * of a single IGVM file. |
| */ |
| typedef struct QIgvm { |
| IgvmHandle file; |
| ConfidentialGuestSupport *cgs; |
| ConfidentialGuestSupportClass *cgsc; |
| uint32_t compatibility_mask; |
| unsigned current_header_index; |
| QTAILQ_HEAD(, QIgvmParameterData) parameter_data; |
| IgvmPlatformType platform_type; |
| |
| /* |
| * SEV-SNP platforms can contain an ID block and authentication |
| * that should be verified by the guest. |
| */ |
| struct sev_id_block *id_block; |
| struct sev_id_authentication *id_auth; |
| |
| /* Define the guest policy for SEV guests */ |
| uint64_t sev_policy; |
| |
| /* These variables keep track of contiguous page regions */ |
| IGVM_VHS_PAGE_DATA region_prev_page_data; |
| uint64_t region_start; |
| unsigned region_start_index; |
| unsigned region_last_index; |
| unsigned region_page_count; |
| } QIgvm; |
| |
| static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_parameter_area(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_parameter_insert(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_environment_info(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_required_memory(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp); |
| static int qigvm_initialization_guest_policy(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp); |
| |
| struct QIGVMHandler { |
| uint32_t type; |
| uint32_t section; |
| int (*handler)(QIgvm *ctx, const uint8_t *header_data, Error **errp); |
| }; |
| |
| static struct QIGVMHandler handlers[] = { |
| { IGVM_VHT_PAGE_DATA, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_page_data }, |
| { IGVM_VHT_VP_CONTEXT, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_vp_context }, |
| { IGVM_VHT_PARAMETER_AREA, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_parameter_area }, |
| { IGVM_VHT_PARAMETER_INSERT, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_parameter_insert }, |
| { IGVM_VHT_MEMORY_MAP, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_memory_map }, |
| { IGVM_VHT_VP_COUNT_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_vp_count }, |
| { IGVM_VHT_ENVIRONMENT_INFO_PARAMETER, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_environment_info }, |
| { IGVM_VHT_REQUIRED_MEMORY, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_required_memory }, |
| { IGVM_VHT_SNP_ID_BLOCK, IGVM_HEADER_SECTION_DIRECTIVE, |
| qigvm_directive_snp_id_block }, |
| { IGVM_VHT_GUEST_POLICY, IGVM_HEADER_SECTION_INITIALIZATION, |
| qigvm_initialization_guest_policy }, |
| }; |
| |
| static int qigvm_handler(QIgvm *ctx, uint32_t type, Error **errp) |
| { |
| size_t handler; |
| IgvmHandle header_handle; |
| const uint8_t *header_data; |
| int result; |
| |
| for (handler = 0; handler < G_N_ELEMENTS(handlers); handler++) { |
| if (handlers[handler].type != type) { |
| continue; |
| } |
| header_handle = igvm_get_header(ctx->file, handlers[handler].section, |
| ctx->current_header_index); |
| if (header_handle < 0) { |
| error_setg( |
| errp, |
| "IGVM file is invalid: Failed to read directive header (code: %d)", |
| (int)header_handle); |
| return -1; |
| } |
| header_data = igvm_get_buffer(ctx->file, header_handle) + |
| sizeof(IGVM_VHS_VARIABLE_HEADER); |
| result = handlers[handler].handler(ctx, header_data, errp); |
| igvm_free_buffer(ctx->file, header_handle); |
| return result; |
| } |
| error_setg(errp, |
| "IGVM: Unknown header type encountered when processing file: " |
| "(type 0x%X)", |
| type); |
| return -1; |
| } |
| |
| static void *qigvm_prepare_memory(QIgvm *ctx, uint64_t addr, uint64_t size, |
| int region_identifier, Error **errp) |
| { |
| ERRP_GUARD(); |
| MemoryRegion *igvm_pages = NULL; |
| Int128 gpa_region_size; |
| MemoryRegionSection mrs = |
| memory_region_find(get_system_memory(), addr, size); |
| if (mrs.mr) { |
| if (!memory_region_is_ram(mrs.mr)) { |
| memory_region_unref(mrs.mr); |
| error_setg( |
| errp, |
| "Processing of IGVM file failed: Could not prepare memory " |
| "at address 0x%lX due to existing non-RAM region", |
| addr); |
| return NULL; |
| } |
| |
| gpa_region_size = int128_make64(size); |
| if (int128_lt(mrs.size, gpa_region_size)) { |
| memory_region_unref(mrs.mr); |
| error_setg( |
| errp, |
| "Processing of IGVM file failed: Could not prepare memory " |
| "at address 0x%lX: region size exceeded", |
| addr); |
| return NULL; |
| } |
| return qemu_map_ram_ptr(mrs.mr->ram_block, mrs.offset_within_region); |
| } else { |
| /* |
| * The region_identifier is the is the index of the IGVM directive that |
| * contains the page with the lowest GPA in the region. This will |
| * generate a unique region name. |
| */ |
| g_autofree char *region_name = |
| g_strdup_printf("igvm.%X", region_identifier); |
| igvm_pages = g_new0(MemoryRegion, 1); |
| if (ctx->cgs && ctx->cgs->require_guest_memfd) { |
| if (!memory_region_init_ram_guest_memfd(igvm_pages, NULL, |
| region_name, size, errp)) { |
| return NULL; |
| } |
| } else { |
| if (!memory_region_init_ram(igvm_pages, NULL, region_name, size, |
| errp)) { |
| return NULL; |
| } |
| } |
| memory_region_add_subregion(get_system_memory(), addr, igvm_pages); |
| return memory_region_get_ram_ptr(igvm_pages); |
| } |
| } |
| |
| static int qigvm_type_to_cgs_type(IgvmPageDataType memory_type, bool unmeasured, |
| bool zero) |
| { |
| switch (memory_type) { |
| case IGVM_PAGE_DATA_TYPE_NORMAL: { |
| if (unmeasured) { |
| return CGS_PAGE_TYPE_UNMEASURED; |
| } else { |
| return zero ? CGS_PAGE_TYPE_ZERO : CGS_PAGE_TYPE_NORMAL; |
| } |
| } |
| case IGVM_PAGE_DATA_TYPE_SECRETS: |
| return CGS_PAGE_TYPE_SECRETS; |
| case IGVM_PAGE_DATA_TYPE_CPUID_DATA: |
| return CGS_PAGE_TYPE_CPUID; |
| case IGVM_PAGE_DATA_TYPE_CPUID_XF: |
| return CGS_PAGE_TYPE_CPUID; |
| default: |
| return -1; |
| } |
| } |
| |
| static bool qigvm_page_attrs_equal(IgvmHandle igvm, unsigned header_index, |
| const IGVM_VHS_PAGE_DATA *page_1, |
| const IGVM_VHS_PAGE_DATA *page_2) |
| { |
| IgvmHandle data_handle1, data_handle2; |
| |
| /* |
| * If one page has data and the other doesn't then this results in different |
| * page types: NORMAL vs ZERO. |
| */ |
| data_handle1 = igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, |
| header_index - 1); |
| data_handle2 = |
| igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, header_index); |
| if ((data_handle1 == IGVMAPI_NO_DATA || |
| data_handle2 == IGVMAPI_NO_DATA) && |
| data_handle1 != data_handle2) { |
| return false; |
| } |
| return ((*(const uint32_t *)&page_1->flags == |
| *(const uint32_t *)&page_2->flags) && |
| (page_1->data_type == page_2->data_type) && |
| (page_1->compatibility_mask == page_2->compatibility_mask)); |
| } |
| |
| static int qigvm_process_mem_region(QIgvm *ctx, unsigned start_index, |
| uint64_t gpa_start, unsigned page_count, |
| const IgvmPageDataFlags *flags, |
| const IgvmPageDataType page_type, |
| Error **errp) |
| { |
| uint8_t *region; |
| IgvmHandle data_handle; |
| const void *data; |
| uint32_t data_size; |
| unsigned page_index; |
| bool zero = true; |
| const uint64_t page_size = flags->is_2mb_page ? 0x200000 : 0x1000; |
| int result; |
| int cgs_page_type; |
| |
| region = qigvm_prepare_memory(ctx, gpa_start, page_count * page_size, |
| start_index, errp); |
| if (!region) { |
| return -1; |
| } |
| |
| for (page_index = 0; page_index < page_count; page_index++) { |
| data_handle = igvm_get_header_data( |
| ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, page_index + start_index); |
| if (data_handle == IGVMAPI_NO_DATA) { |
| /* No data indicates a zero page */ |
| memset(®ion[page_index * page_size], 0, page_size); |
| } else if (data_handle < 0) { |
| error_setg( |
| errp, |
| "IGVM file contains invalid page data for directive with " |
| "index %d", |
| page_index + start_index); |
| return -1; |
| } else { |
| zero = false; |
| data_size = igvm_get_buffer_size(ctx->file, data_handle); |
| if (data_size < page_size) { |
| memset(®ion[page_index * page_size], 0, page_size); |
| } else if (data_size > page_size) { |
| error_setg(errp, |
| "IGVM file contains page data with invalid size for " |
| "directive with index %d", |
| page_index + start_index); |
| return -1; |
| } |
| data = igvm_get_buffer(ctx->file, data_handle); |
| memcpy(®ion[page_index * page_size], data, data_size); |
| igvm_free_buffer(ctx->file, data_handle); |
| } |
| } |
| |
| /* |
| * If a confidential guest support object is provided then use it to set the |
| * guest state. |
| */ |
| if (ctx->cgs) { |
| cgs_page_type = |
| qigvm_type_to_cgs_type(page_type, flags->unmeasured, zero); |
| if (cgs_page_type < 0) { |
| error_setg(errp, |
| "Invalid page type in IGVM file. Directives: %d to %d, " |
| "page type: %d", |
| start_index, start_index + page_count, page_type); |
| return -1; |
| } |
| |
| result = ctx->cgsc->set_guest_state( |
| gpa_start, region, page_size * page_count, cgs_page_type, 0, errp); |
| if (result < 0) { |
| return result; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_process_mem_page(QIgvm *ctx, |
| const IGVM_VHS_PAGE_DATA *page_data, |
| Error **errp) |
| { |
| if (page_data) { |
| if (ctx->region_page_count == 0) { |
| ctx->region_start = page_data->gpa; |
| ctx->region_start_index = ctx->current_header_index; |
| } else { |
| if (!qigvm_page_attrs_equal(ctx->file, ctx->current_header_index, |
| page_data, |
| &ctx->region_prev_page_data) || |
| ((ctx->region_prev_page_data.gpa + |
| (ctx->region_prev_page_data.flags.is_2mb_page ? 0x200000 : |
| 0x1000)) != |
| page_data->gpa) || |
| (ctx->region_last_index != (ctx->current_header_index - 1))) { |
| /* End of current region */ |
| if (qigvm_process_mem_region( |
| ctx, ctx->region_start_index, ctx->region_start, |
| ctx->region_page_count, |
| &ctx->region_prev_page_data.flags, |
| ctx->region_prev_page_data.data_type, errp) < 0) { |
| return -1; |
| } |
| ctx->region_page_count = 0; |
| ctx->region_start = page_data->gpa; |
| ctx->region_start_index = ctx->current_header_index; |
| } |
| } |
| memcpy(&ctx->region_prev_page_data, page_data, |
| sizeof(ctx->region_prev_page_data)); |
| ctx->region_last_index = ctx->current_header_index; |
| ctx->region_page_count++; |
| } else { |
| if (ctx->region_page_count > 0) { |
| if (qigvm_process_mem_region( |
| ctx, ctx->region_start_index, ctx->region_start, |
| ctx->region_page_count, &ctx->region_prev_page_data.flags, |
| ctx->region_prev_page_data.data_type, errp) < 0) { |
| return -1; |
| } |
| ctx->region_page_count = 0; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PAGE_DATA *page_data = |
| (const IGVM_VHS_PAGE_DATA *)header_data; |
| if (page_data->compatibility_mask & ctx->compatibility_mask) { |
| return qigvm_process_mem_page(ctx, page_data, errp); |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_VP_CONTEXT *vp_context = |
| (const IGVM_VHS_VP_CONTEXT *)header_data; |
| IgvmHandle data_handle; |
| uint8_t *data; |
| int result; |
| |
| if (!(vp_context->compatibility_mask & ctx->compatibility_mask)) { |
| return 0; |
| } |
| |
| /* |
| * A confidential guest support object must be provided for setting |
| * a VP context. |
| */ |
| if (!ctx->cgs) { |
| error_setg( |
| errp, |
| "A VP context is present in the IGVM file but is not supported " |
| "by the current system."); |
| return -1; |
| } |
| |
| data_handle = igvm_get_header_data(ctx->file, IGVM_HEADER_SECTION_DIRECTIVE, |
| ctx->current_header_index); |
| if (data_handle < 0) { |
| error_setg(errp, "Invalid VP context in IGVM file. Error code: %X", |
| data_handle); |
| return -1; |
| } |
| |
| data = (uint8_t *)igvm_get_buffer(ctx->file, data_handle); |
| result = ctx->cgsc->set_guest_state( |
| vp_context->gpa, data, igvm_get_buffer_size(ctx->file, data_handle), |
| CGS_PAGE_TYPE_VMSA, vp_context->vp_index, errp); |
| igvm_free_buffer(ctx->file, data_handle); |
| if (result < 0) { |
| return result; |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_parameter_area(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PARAMETER_AREA *param_area = |
| (const IGVM_VHS_PARAMETER_AREA *)header_data; |
| QIgvmParameterData *param_entry; |
| |
| param_entry = g_new0(QIgvmParameterData, 1); |
| param_entry->size = param_area->number_of_bytes; |
| param_entry->index = param_area->parameter_area_index; |
| param_entry->data = g_malloc0(param_entry->size); |
| |
| QTAILQ_INSERT_TAIL(&ctx->parameter_data, param_entry, next); |
| return 0; |
| } |
| |
| static int qigvm_directive_parameter_insert(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PARAMETER_INSERT *param = |
| (const IGVM_VHS_PARAMETER_INSERT *)header_data; |
| QIgvmParameterData *param_entry; |
| int result; |
| void *region; |
| |
| if (!(param->compatibility_mask & ctx->compatibility_mask)) { |
| return 0; |
| } |
| |
| QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) |
| { |
| if (param_entry->index == param->parameter_area_index) { |
| region = qigvm_prepare_memory(ctx, param->gpa, param_entry->size, |
| ctx->current_header_index, errp); |
| if (!region) { |
| return -1; |
| } |
| memcpy(region, param_entry->data, param_entry->size); |
| g_free(param_entry->data); |
| param_entry->data = NULL; |
| |
| /* |
| * If a confidential guest support object is provided then use it to |
| * set the guest state. |
| */ |
| if (ctx->cgs) { |
| result = ctx->cgsc->set_guest_state(param->gpa, region, |
| param_entry->size, |
| CGS_PAGE_TYPE_UNMEASURED, 0, |
| errp); |
| if (result < 0) { |
| return -1; |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_cmp_mm_entry(const void *a, const void *b) |
| { |
| const IGVM_VHS_MEMORY_MAP_ENTRY *entry_a = |
| (const IGVM_VHS_MEMORY_MAP_ENTRY *)a; |
| const IGVM_VHS_MEMORY_MAP_ENTRY *entry_b = |
| (const IGVM_VHS_MEMORY_MAP_ENTRY *)b; |
| if (entry_a->starting_gpa_page_number < entry_b->starting_gpa_page_number) { |
| return -1; |
| } else if (entry_a->starting_gpa_page_number > |
| entry_b->starting_gpa_page_number) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; |
| QIgvmParameterData *param_entry; |
| int max_entry_count; |
| int entry = 0; |
| IGVM_VHS_MEMORY_MAP_ENTRY *mm_entry; |
| ConfidentialGuestMemoryMapEntry cgmm_entry; |
| int retval = 0; |
| |
| if (!ctx->cgs) { |
| error_setg(errp, |
| "IGVM file contains a memory map but this is not supported " |
| "by the current system."); |
| return -1; |
| } |
| |
| /* Find the parameter area that should hold the memory map */ |
| QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) |
| { |
| if (param_entry->index == param->parameter_area_index) { |
| max_entry_count = |
| param_entry->size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY); |
| mm_entry = (IGVM_VHS_MEMORY_MAP_ENTRY *)param_entry->data; |
| |
| retval = ctx->cgsc->get_mem_map_entry(entry, &cgmm_entry, errp); |
| while (retval == 0) { |
| if (entry > max_entry_count) { |
| error_setg( |
| errp, |
| "IGVM: guest memory map size exceeds parameter area defined in IGVM file"); |
| return -1; |
| } |
| mm_entry[entry].starting_gpa_page_number = cgmm_entry.gpa >> 12; |
| mm_entry[entry].number_of_pages = cgmm_entry.size >> 12; |
| |
| switch (cgmm_entry.type) { |
| case CGS_MEM_RAM: |
| mm_entry[entry].entry_type = |
| IGVM_MEMORY_MAP_ENTRY_TYPE_MEMORY; |
| break; |
| case CGS_MEM_RESERVED: |
| mm_entry[entry].entry_type = |
| IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; |
| break; |
| case CGS_MEM_ACPI: |
| mm_entry[entry].entry_type = |
| IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; |
| break; |
| case CGS_MEM_NVS: |
| mm_entry[entry].entry_type = |
| IGVM_MEMORY_MAP_ENTRY_TYPE_PERSISTENT; |
| break; |
| case CGS_MEM_UNUSABLE: |
| mm_entry[entry].entry_type = |
| IGVM_MEMORY_MAP_ENTRY_TYPE_PLATFORM_RESERVED; |
| break; |
| } |
| retval = |
| ctx->cgsc->get_mem_map_entry(++entry, &cgmm_entry, errp); |
| } |
| if (retval < 0) { |
| return retval; |
| } |
| /* The entries need to be sorted */ |
| qsort(mm_entry, entry, sizeof(IGVM_VHS_MEMORY_MAP_ENTRY), |
| qigvm_cmp_mm_entry); |
| |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; |
| QIgvmParameterData *param_entry; |
| uint32_t *vp_count; |
| CPUState *cpu; |
| |
| QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) |
| { |
| if (param_entry->index == param->parameter_area_index) { |
| vp_count = (uint32_t *)(param_entry->data + param->byte_offset); |
| *vp_count = 0; |
| CPU_FOREACH(cpu) |
| { |
| (*vp_count)++; |
| } |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_environment_info(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; |
| QIgvmParameterData *param_entry; |
| IgvmEnvironmentInfo *environmental_state; |
| |
| QTAILQ_FOREACH(param_entry, &ctx->parameter_data, next) |
| { |
| if (param_entry->index == param->parameter_area_index) { |
| environmental_state = |
| (IgvmEnvironmentInfo *)(param_entry->data + param->byte_offset); |
| environmental_state->memory_is_shared = 1; |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_required_memory(QIgvm *ctx, |
| const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_REQUIRED_MEMORY *mem = |
| (const IGVM_VHS_REQUIRED_MEMORY *)header_data; |
| uint8_t *region; |
| int result; |
| |
| if (!(mem->compatibility_mask & ctx->compatibility_mask)) { |
| return 0; |
| } |
| |
| region = qigvm_prepare_memory(ctx, mem->gpa, mem->number_of_bytes, |
| ctx->current_header_index, errp); |
| if (!region) { |
| return -1; |
| } |
| if (ctx->cgs) { |
| result = |
| ctx->cgsc->set_guest_state(mem->gpa, region, mem->number_of_bytes, |
| CGS_PAGE_TYPE_REQUIRED_MEMORY, 0, errp); |
| if (result < 0) { |
| return result; |
| } |
| } |
| return 0; |
| } |
| |
| static int qigvm_directive_snp_id_block(QIgvm *ctx, const uint8_t *header_data, |
| Error **errp) |
| { |
| const IGVM_VHS_SNP_ID_BLOCK *igvm_id = |
| (const IGVM_VHS_SNP_ID_BLOCK *)header_data; |
| |
| if (!(igvm_id->compatibility_mask & ctx->compatibility_mask)) { |
| return 0; |
| } |
| |
| if (ctx->id_block) { |
| error_setg(errp, "IGVM: Multiple ID blocks encountered " |
| "in IGVM file."); |
| return -1; |
| } |
| ctx->id_block = g_new0(struct sev_id_block, 1); |
| ctx->id_auth = g_new0(struct sev_id_authentication, 1); |
| |
| memcpy(ctx->id_block->family_id, igvm_id->family_id, |
| sizeof(ctx->id_block->family_id)); |
| memcpy(ctx->id_block->image_id, igvm_id->image_id, |
| sizeof(ctx->id_block->image_id)); |
| ctx->id_block->guest_svn = igvm_id->guest_svn; |
| ctx->id_block->version = IGVM_SEV_ID_BLOCK_VERSION; |
| memcpy(ctx->id_block->ld, igvm_id->ld, sizeof(ctx->id_block->ld)); |
| |
| ctx->id_auth->id_key_alg = igvm_id->id_key_algorithm; |
| assert(sizeof(igvm_id->id_key_signature) <= |
| sizeof(ctx->id_auth->id_block_sig)); |
| memcpy(ctx->id_auth->id_block_sig, &igvm_id->id_key_signature, |
| sizeof(igvm_id->id_key_signature)); |
| |
| ctx->id_auth->auth_key_algo = igvm_id->author_key_algorithm; |
| assert(sizeof(igvm_id->author_key_signature) <= |
| sizeof(ctx->id_auth->id_key_sig)); |
| memcpy(ctx->id_auth->id_key_sig, &igvm_id->author_key_signature, |
| sizeof(igvm_id->author_key_signature)); |
| |
| /* |
| * SEV and IGVM public key structure population are slightly different. |
| * See SEV Secure Nested Paging Firmware ABI Specification, Chapter 10. |
| */ |
| *((uint32_t *)ctx->id_auth->id_key) = igvm_id->id_public_key.curve; |
| memcpy(&ctx->id_auth->id_key[4], &igvm_id->id_public_key.qx, 72); |
| memcpy(&ctx->id_auth->id_key[76], &igvm_id->id_public_key.qy, 72); |
| |
| *((uint32_t *)ctx->id_auth->author_key) = |
| igvm_id->author_public_key.curve; |
| memcpy(&ctx->id_auth->author_key[4], &igvm_id->author_public_key.qx, |
| 72); |
| memcpy(&ctx->id_auth->author_key[76], &igvm_id->author_public_key.qy, |
| 72); |
| |
| return 0; |
| } |
| |
| static int qigvm_initialization_guest_policy(QIgvm *ctx, |
| const uint8_t *header_data, Error **errp) |
| { |
| const IGVM_VHS_GUEST_POLICY *guest = |
| (const IGVM_VHS_GUEST_POLICY *)header_data; |
| |
| if (guest->compatibility_mask & ctx->compatibility_mask) { |
| ctx->sev_policy = guest->policy; |
| } |
| return 0; |
| } |
| |
| static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp) |
| { |
| int32_t header_count; |
| unsigned header_index; |
| IgvmHandle header_handle; |
| IGVM_VHS_SUPPORTED_PLATFORM *platform; |
| uint32_t compatibility_mask_sev = 0; |
| uint32_t compatibility_mask_sev_es = 0; |
| uint32_t compatibility_mask_sev_snp = 0; |
| uint32_t compatibility_mask = 0; |
| |
| header_count = igvm_header_count(ctx->file, IGVM_HEADER_SECTION_PLATFORM); |
| if (header_count < 0) { |
| error_setg(errp, |
| "Invalid platform header count in IGVM file. Error code: %X", |
| header_count); |
| return -1; |
| } |
| |
| for (header_index = 0; header_index < (unsigned)header_count; |
| header_index++) { |
| IgvmVariableHeaderType typ = igvm_get_header_type( |
| ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); |
| if (typ == IGVM_VHT_SUPPORTED_PLATFORM) { |
| header_handle = igvm_get_header( |
| ctx->file, IGVM_HEADER_SECTION_PLATFORM, header_index); |
| if (header_handle < 0) { |
| error_setg(errp, |
| "Invalid platform header in IGVM file. " |
| "Index: %d, Error code: %X", |
| header_index, header_handle); |
| return -1; |
| } |
| platform = |
| (IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(ctx->file, |
| header_handle) + |
| sizeof( |
| IGVM_VHS_VARIABLE_HEADER)); |
| if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV_ES) && |
| ctx->cgs) { |
| if (ctx->cgsc->check_support( |
| CGS_PLATFORM_SEV_ES, platform->platform_version, |
| platform->highest_vtl, platform->shared_gpa_boundary)) { |
| compatibility_mask_sev_es = platform->compatibility_mask; |
| } |
| } else if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV) && |
| ctx->cgs) { |
| if (ctx->cgsc->check_support( |
| CGS_PLATFORM_SEV, platform->platform_version, |
| platform->highest_vtl, platform->shared_gpa_boundary)) { |
| compatibility_mask_sev = platform->compatibility_mask; |
| } |
| } else if ((platform->platform_type == |
| IGVM_PLATFORM_TYPE_SEV_SNP) && |
| ctx->cgs) { |
| if (ctx->cgsc->check_support( |
| CGS_PLATFORM_SEV_SNP, platform->platform_version, |
| platform->highest_vtl, platform->shared_gpa_boundary)) { |
| compatibility_mask_sev_snp = platform->compatibility_mask; |
| } |
| } else if (platform->platform_type == IGVM_PLATFORM_TYPE_NATIVE) { |
| compatibility_mask = platform->compatibility_mask; |
| } |
| igvm_free_buffer(ctx->file, header_handle); |
| } |
| } |
| /* Choose the strongest supported isolation technology */ |
| if (compatibility_mask_sev_snp != 0) { |
| ctx->compatibility_mask = compatibility_mask_sev_snp; |
| ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_SNP; |
| } else if (compatibility_mask_sev_es != 0) { |
| ctx->compatibility_mask = compatibility_mask_sev_es; |
| ctx->platform_type = IGVM_PLATFORM_TYPE_SEV_ES; |
| } else if (compatibility_mask_sev != 0) { |
| ctx->compatibility_mask = compatibility_mask_sev; |
| ctx->platform_type = IGVM_PLATFORM_TYPE_SEV; |
| } else if (compatibility_mask != 0) { |
| ctx->compatibility_mask = compatibility_mask; |
| ctx->platform_type = IGVM_PLATFORM_TYPE_NATIVE; |
| } else { |
| error_setg( |
| errp, |
| "IGVM file does not describe a compatible supported platform"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int qigvm_handle_policy(QIgvm *ctx, Error **errp) |
| { |
| if (ctx->platform_type == IGVM_PLATFORM_TYPE_SEV_SNP) { |
| int id_block_len = 0; |
| int id_auth_len = 0; |
| if (ctx->id_block) { |
| ctx->id_block->policy = ctx->sev_policy; |
| id_block_len = sizeof(struct sev_id_block); |
| id_auth_len = sizeof(struct sev_id_authentication); |
| } |
| return ctx->cgsc->set_guest_policy(GUEST_POLICY_SEV, ctx->sev_policy, |
| ctx->id_block, id_block_len, |
| ctx->id_auth, id_auth_len, errp); |
| } |
| return 0; |
| } |
| |
| static IgvmHandle qigvm_file_init(char *filename, Error **errp) |
| { |
| IgvmHandle igvm; |
| g_autofree uint8_t *buf = NULL; |
| unsigned long len; |
| g_autoptr(GError) gerr = NULL; |
| |
| if (!g_file_get_contents(filename, (gchar **)&buf, &len, &gerr)) { |
| error_setg(errp, "Unable to load %s: %s", filename, gerr->message); |
| return -1; |
| } |
| |
| igvm = igvm_new_from_binary(buf, len); |
| if (igvm < 0) { |
| error_setg(errp, "Unable to parse IGVM file %s: %d", filename, igvm); |
| return -1; |
| } |
| return igvm; |
| } |
| |
| int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs, |
| bool onlyVpContext, Error **errp) |
| { |
| int32_t header_count; |
| QIgvmParameterData *parameter; |
| int retval = -1; |
| QIgvm ctx; |
| |
| memset(&ctx, 0, sizeof(ctx)); |
| ctx.file = qigvm_file_init(cfg->filename, errp); |
| if (ctx.file < 0) { |
| return -1; |
| } |
| |
| /* |
| * The ConfidentialGuestSupport object is optional and allows a confidential |
| * guest platform to perform extra processing, such as page measurement, on |
| * IGVM directives. |
| */ |
| ctx.cgs = cgs; |
| ctx.cgsc = cgs ? CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(cgs) : NULL; |
| |
| /* |
| * Check that the IGVM file provides configuration for the current |
| * platform |
| */ |
| if (qigvm_supported_platform_compat_mask(&ctx, errp) < 0) { |
| goto cleanup; |
| } |
| |
| header_count = igvm_header_count(ctx.file, IGVM_HEADER_SECTION_DIRECTIVE); |
| if (header_count <= 0) { |
| error_setg( |
| errp, "Invalid directive header count in IGVM file. Error code: %X", |
| header_count); |
| goto cleanup; |
| } |
| |
| QTAILQ_INIT(&ctx.parameter_data); |
| |
| for (ctx.current_header_index = 0; |
| ctx.current_header_index < (unsigned)header_count; |
| ctx.current_header_index++) { |
| IgvmVariableHeaderType type = igvm_get_header_type( |
| ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index); |
| if (!onlyVpContext || (type == IGVM_VHT_VP_CONTEXT)) { |
| if (qigvm_handler(&ctx, type, errp) < 0) { |
| goto cleanup_parameters; |
| } |
| } |
| } |
| |
| /* |
| * If only processing the VP context then we don't need to process |
| * any more of the file. |
| */ |
| if (onlyVpContext) { |
| retval = 0; |
| goto cleanup_parameters; |
| } |
| |
| header_count = |
| igvm_header_count(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION); |
| if (header_count < 0) { |
| error_setg( |
| errp, |
| "Invalid initialization header count in IGVM file. Error code: %X", |
| header_count); |
| goto cleanup_parameters; |
| } |
| |
| for (ctx.current_header_index = 0; |
| ctx.current_header_index < (unsigned)header_count; |
| ctx.current_header_index++) { |
| IgvmVariableHeaderType type = |
| igvm_get_header_type(ctx.file, IGVM_HEADER_SECTION_INITIALIZATION, |
| ctx.current_header_index); |
| if (qigvm_handler(&ctx, type, errp) < 0) { |
| goto cleanup_parameters; |
| } |
| } |
| |
| /* |
| * Contiguous pages of data with compatible flags are grouped together in |
| * order to reduce the number of memory regions we create. Make sure the |
| * last group is processed with this call. |
| */ |
| retval = qigvm_process_mem_page(&ctx, NULL, errp); |
| |
| if (retval == 0) { |
| retval = qigvm_handle_policy(&ctx, errp); |
| } |
| |
| cleanup_parameters: |
| QTAILQ_FOREACH(parameter, &ctx.parameter_data, next) |
| { |
| g_free(parameter->data); |
| parameter->data = NULL; |
| } |
| g_free(ctx.id_block); |
| g_free(ctx.id_auth); |
| |
| cleanup: |
| igvm_free(ctx.file); |
| |
| return retval; |
| } |