/* * QEMU IGVM configuration backend for guests * * Copyright (C) 2023-2024 SUSE * * Authors: * Roy Hopkins * * 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 #include 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; }