| /* | |
| * 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 "qapi/error.h" | |
| #include "qemu/error-report.h" | |
| #include "qemu/target-info-qapi.h" | |
| #include "migration/vmstate.h" | |
| #include "system/igvm.h" | |
| #include "system/igvm-cfg.h" | |
| #include "system/igvm-internal.h" | |
| #include "system/memory.h" | |
| #include "system/address-spaces.h" | |
| #include "hw/core/cpu.h" | |
| #include "trace.h" | |
| #include <igvm/igvm.h> | |
| #include <igvm/igvm_defs.h> | |
| #ifdef CONFIG_FDT | |
| #include <libfdt.h> | |
| /* Enough for the header, the memory reservation block and an empty root node */ | |
| #define EMPTY_FDT_SIZE 256 | |
| #endif | |
| #ifndef IGVM_VHT_OPTIONAL_BIT | |
| #define IGVM_VHT_OPTIONAL_BIT (1U << 31) | |
| #endif | |
| /* | |
| * Bit 31 of the variable header type indicates that the header is | |
| * optional and can be safely ignored by a loader that does not | |
| * support it. If the bit is clear, the file cannot be loaded. | |
| * https://docs.rs/igvm_defs/0.4.0/igvm_defs/struct.IgvmVariableHeaderType.html | |
| */ | |
| static IgvmVariableHeaderType igvm_vht_type(IgvmVariableHeaderType type) | |
| { | |
| return type & ~IGVM_VHT_OPTIONAL_BIT; | |
| } | |
| static bool igvm_vht_optional(IgvmVariableHeaderType type) | |
| { | |
| return !!(type & IGVM_VHT_OPTIONAL_BIT); | |
| } | |
| /* | |
| * 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 | |
| static QIgvmParameterData* | |
| qigvm_find_param_entry(QIgvm *igvm, uint32_t parameter_area_index, | |
| Error **errp) | |
| { | |
| QIgvmParameterData *param_entry; | |
| QTAILQ_FOREACH(param_entry, &igvm->parameter_data, next) | |
| { | |
| if (param_entry->index == parameter_area_index) { | |
| return param_entry; | |
| } | |
| } | |
| error_setg(errp, "IGVM: parameter area index %u not found", | |
| parameter_area_index); | |
| return NULL; | |
| } | |
| /* | |
| * Get parameter area data at byte_offset with bounds validation. | |
| * On success, returns offset-adjusted data pointer and sets param_size | |
| * to remaining space. | |
| * Returns NULL on failure. | |
| */ | |
| uint8_t * | |
| qigvm_get_param_data(QIgvm *igvm, const IGVM_VHS_PARAMETER *param, | |
| uint32_t *param_size, | |
| Error **errp) | |
| { | |
| QIgvmParameterData *param_entry; | |
| assert(param_size); | |
| param_entry = qigvm_find_param_entry(igvm, param->parameter_area_index, | |
| errp); | |
| if (!param_entry) { | |
| return NULL; | |
| } | |
| if (param->byte_offset > param_entry->size) { | |
| error_setg(errp, | |
| "IGVM: byte_offset 0x%x exceeds parameter area size 0x%x", | |
| param->byte_offset, param_entry->size); | |
| return NULL; | |
| } | |
| *param_size = param_entry->size - param->byte_offset; | |
| return param_entry->data + param->byte_offset; | |
| } | |
| 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); | |
| static int qigvm_directive_command_line(QIgvm *ctx, const uint8_t *header_data, | |
| Error **errp); | |
| #ifdef CONFIG_FDT | |
| static int qigvm_directive_device_tree(QIgvm *ctx, const uint8_t *header_data, | |
| Error **errp); | |
| #endif | |
| struct QIGVMHandler { | |
| IgvmVariableHeaderType type; | |
| IgvmHeaderSection 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 }, | |
| { IGVM_VHT_MADT, IGVM_HEADER_SECTION_DIRECTIVE, | |
| qigvm_directive_madt }, | |
| { IGVM_VHT_COMMAND_LINE, IGVM_HEADER_SECTION_DIRECTIVE, | |
| qigvm_directive_command_line }, | |
| #ifdef CONFIG_FDT | |
| { IGVM_VHT_DEVICE_TREE, IGVM_HEADER_SECTION_DIRECTIVE, | |
| qigvm_directive_device_tree }, | |
| #endif | |
| }; | |
| static int qigvm_handler(QIgvm *ctx, IgvmVariableHeaderType raw_type, | |
| Error **errp) | |
| { | |
| size_t handler; | |
| IgvmHandle header_handle; | |
| const uint8_t *header_data; | |
| int result; | |
| IgvmVariableHeaderType type = igvm_vht_type(raw_type); | |
| for (handler = 0; handler < G_N_ELEMENTS(handlers); handler++) { | |
| if (handlers[handler].type != type) { | |
| continue; | |
| } | |
| header_handle = igvm_get_header(ctx->cfg->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->cfg->file, header_handle); | |
| if (header_data != NULL) { | |
| header_data += sizeof(IGVM_VHS_VARIABLE_HEADER); | |
| result = handlers[handler].handler(ctx, header_data, errp); | |
| } else { | |
| error_setg(errp, | |
| "IGVM: No buffer for handle %d: " | |
| "(type 0x%X)", | |
| header_handle, type); | |
| result = -1; | |
| } | |
| igvm_free_buffer(ctx->cfg->file, header_handle); | |
| return result; | |
| } | |
| if (igvm_vht_optional(raw_type)) { | |
| warn_report("IGVM: Skipping unsupported optional header type 0x%" | |
| PRIX32, type); | |
| return 0; | |
| } | |
| 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(); | |
| IgvmMemoryRegion *imr = 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%" PRIx64 " 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%" PRIx64 ": region size 0x%" PRIx64 " exceeded", | |
| addr, size); | |
| 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); | |
| imr = g_new0(IgvmMemoryRegion, 1); | |
| imr->mr = g_new0(MemoryRegion, 1); | |
| if (ctx->machine_state->cgs && | |
| ctx->machine_state->cgs->require_guest_memfd) { | |
| if (!memory_region_init_ram_guest_memfd_private( | |
| imr->mr, NULL, region_name, size, errp)) { | |
| g_free(imr->mr); | |
| g_free(imr); | |
| return NULL; | |
| } | |
| } else { | |
| if (!memory_region_init_ram(imr->mr, NULL, region_name, size, | |
| errp)) { | |
| g_free(imr->mr); | |
| g_free(imr); | |
| return NULL; | |
| } | |
| } | |
| memory_region_add_subregion(get_system_memory(), addr, imr->mr); | |
| QTAILQ_INSERT_TAIL(&ctx->cfg->memory_regions, imr, next); | |
| return memory_region_get_ram_ptr(imr->mr); | |
| } | |
| } | |
| 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->cfg->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->cfg->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->cfg->file, data_handle); | |
| if (data == NULL) { | |
| error_setg(errp, "IGVM: No buffer for handle %d", data_handle); | |
| igvm_free_buffer(ctx->cfg->file, data_handle); | |
| return -1; | |
| } | |
| memcpy(®ion[page_index * page_size], data, data_size); | |
| igvm_free_buffer(ctx->cfg->file, data_handle); | |
| } | |
| } | |
| /* | |
| * If a confidential guest support object is provided then use it to set the | |
| * guest state. | |
| */ | |
| if (ctx->machine_state->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->cfg->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; | |
| } | |
| data_handle = igvm_get_header_data(ctx->cfg->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->cfg->file, data_handle); | |
| if (data == NULL) { | |
| error_setg(errp, "IGVM: No buffer for handle %d", data_handle); | |
| result = -1; | |
| goto exit; | |
| } | |
| if (ctx->machine_state->cgs) { | |
| result = ctx->cgsc->set_guest_state( | |
| vp_context->gpa, data, | |
| igvm_get_buffer_size(ctx->cfg->file, data_handle), | |
| CGS_PAGE_TYPE_VMSA, vp_context->vp_index, errp); | |
| } else if (target_arch() == SYS_EMU_TARGET_X86_64) { | |
| result = qigvm_x86_set_vp_context(data, vp_context->vp_index, errp); | |
| } else { | |
| error_setg( | |
| errp, | |
| "A VP context is present in the IGVM file but is not supported " | |
| "by the current system."); | |
| result = -1; | |
| } | |
| exit: | |
| igvm_free_buffer(ctx->cfg->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; | |
| } | |
| param_entry = qigvm_find_param_entry(ctx, | |
| param->parameter_area_index, errp); | |
| if (param_entry == NULL) { | |
| return -1; | |
| } | |
| 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->machine_state->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; | |
| int (*get_mem_map_entry)(int index, ConfidentialGuestMemoryMapEntry *entry, | |
| Error **errp) = NULL; | |
| uint32_t param_size; | |
| int max_entry_count; | |
| int entry = 0; | |
| IGVM_VHS_MEMORY_MAP_ENTRY *mm_entry; | |
| ConfidentialGuestMemoryMapEntry cgmm_entry; | |
| int retval = 0; | |
| if (ctx->machine_state->cgs && ctx->cgsc->get_mem_map_entry) { | |
| get_mem_map_entry = ctx->cgsc->get_mem_map_entry; | |
| } else if (target_arch() == SYS_EMU_TARGET_X86_64) { | |
| get_mem_map_entry = qigvm_x86_get_mem_map_entry; | |
| } else { | |
| 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 */ | |
| mm_entry = | |
| (IGVM_VHS_MEMORY_MAP_ENTRY *)qigvm_get_param_data(ctx, param, | |
| ¶m_size, errp); | |
| if (!mm_entry) { | |
| return -1; | |
| } | |
| max_entry_count = param_size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY); | |
| retval = 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 = 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); | |
| 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; | |
| uint32_t param_size; | |
| uint32_t *vp_count; | |
| CPUState *cpu; | |
| vp_count = (uint32_t *)qigvm_get_param_data(ctx, param, ¶m_size, errp); | |
| if (!vp_count) { | |
| return -1; | |
| } | |
| if (sizeof(*vp_count) > param_size) { | |
| error_setg(errp, | |
| "IGVM: vp-count parameter exceeds parameter area " | |
| "defined in IGVM file"); | |
| return -1; | |
| } | |
| *vp_count = 0; | |
| CPU_FOREACH(cpu) | |
| { | |
| (*vp_count)++; | |
| } | |
| 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; | |
| uint32_t param_size; | |
| IgvmEnvironmentInfo *environmental_state; | |
| environmental_state = | |
| (IgvmEnvironmentInfo *)qigvm_get_param_data(ctx, param, ¶m_size, | |
| errp); | |
| if (!environmental_state) { | |
| return -1; | |
| } | |
| if (sizeof(*environmental_state) > param_size) { | |
| error_setg(errp, | |
| "IGVM: environment-info parameter exceeds parameter area " | |
| "defined in IGVM file"); | |
| return -1; | |
| } | |
| environmental_state->memory_is_shared = 1; | |
| 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->machine_state->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); | |
| if (ctx->cgsc) { | |
| uint64_t policy; | |
| if (ctx->cgsc->get_guest_policy(GUEST_POLICY_SEV, &policy, errp) < 0) { | |
| return -1; | |
| } | |
| ctx->id_block->policy = policy; | |
| return ctx->cgsc->set_id_block(ctx->id_block, | |
| sizeof(struct sev_id_block), | |
| ctx->id_auth, | |
| sizeof(struct sev_id_authentication), | |
| errp); | |
| } | |
| return 0; | |
| } | |
| static int qigvm_directive_command_line(QIgvm *ctx, const uint8_t *header_data, | |
| Error **errp) | |
| { | |
| const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; | |
| const char *cmdline = ctx->cfg->cmdline ? ctx->cfg->cmdline : ""; | |
| uint8_t *param_data; | |
| uint32_t param_size; | |
| size_t cmdline_size = strlen(cmdline) + 1; | |
| param_data = qigvm_get_param_data(ctx, param, ¶m_size, errp); | |
| if (!param_data) { | |
| return -1; | |
| } | |
| if (cmdline_size > param_size) { | |
| error_setg(errp, | |
| "IGVM: command line size %zu exceeds the %u byte " | |
| "parameter area defined in IGVM file", | |
| cmdline_size, param_size); | |
| return -1; | |
| } | |
| memcpy(param_data, cmdline, cmdline_size); | |
| return 0; | |
| } | |
| #ifdef CONFIG_FDT | |
| static int qigvm_directive_device_tree(QIgvm *ctx, const uint8_t *header_data, | |
| Error **errp) | |
| { | |
| const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data; | |
| g_autofree void *fdt = NULL; | |
| uint8_t *param_data; | |
| uint32_t param_size; | |
| uint32_t fdt_size; | |
| param_data = qigvm_get_param_data(ctx, param, ¶m_size, errp); | |
| if (!param_data) { | |
| return -1; | |
| } | |
| if (ctx->machine_state->fdt) { | |
| fdt = g_memdup2(ctx->machine_state->fdt, | |
| fdt_totalsize(ctx->machine_state->fdt)); | |
| } else { | |
| /* | |
| * The machine doesn't build a device tree of its own. | |
| * Supply an empty tree rather than rejecting the IGVM file. | |
| */ | |
| fdt = g_malloc0(EMPTY_FDT_SIZE); | |
| if (fdt_create_empty_tree(fdt, EMPTY_FDT_SIZE)) { | |
| error_setg(errp, "IGVM: failed to create an empty device tree"); | |
| return -1; | |
| } | |
| } | |
| if (fdt_pack(fdt)) { | |
| error_setg(errp, "IGVM: failed to pack device tree"); | |
| return -1; | |
| } | |
| fdt_size = fdt_totalsize(fdt); | |
| if (fdt_size > param_size) { | |
| error_setg(errp, | |
| "IGVM: device tree size exceeds parameter area" | |
| " defined in IGVM file"); | |
| return -1; | |
| } | |
| memcpy(param_data, fdt, fdt_size); | |
| return 0; | |
| } | |
| #endif | |
| 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) { | |
| if (ctx->cgsc) { | |
| return ctx->cgsc->set_guest_policy(GUEST_POLICY_SEV, | |
| guest->policy, errp); | |
| } | |
| } | |
| 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->cfg->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->cfg->file, IGVM_HEADER_SECTION_PLATFORM, header_index); | |
| typ = igvm_vht_type(typ); | |
| if (typ == IGVM_VHT_SUPPORTED_PLATFORM) { | |
| header_handle = igvm_get_header( | |
| ctx->cfg->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->cfg->file, | |
| header_handle)); | |
| if (platform == NULL) { | |
| error_setg(errp, "IGVM: No buffer for handle %d", header_handle); | |
| igvm_free_buffer(ctx->cfg->file, header_handle); | |
| return -1; | |
| } | |
| platform = (IGVM_VHS_SUPPORTED_PLATFORM *)((void *)platform | |
| + sizeof(IGVM_VHS_VARIABLE_HEADER)); | |
| if ((platform->platform_type == IGVM_PLATFORM_TYPE_SEV_ES) && | |
| ctx->machine_state->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->machine_state->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->machine_state->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->cfg->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; | |
| } | |
| 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; | |
| } | |
| trace_igvm_file_loaded(filename, igvm); | |
| return igvm; | |
| } | |
| int qigvm_process_file(IgvmCfg *cfg, MachineState *machine_state, | |
| bool onlyVpContext, Error **errp) | |
| { | |
| int32_t header_count; | |
| QIgvmParameterData *parameter, *tmp; | |
| int retval = -1; | |
| QIgvm ctx; | |
| memset(&ctx, 0, sizeof(ctx)); | |
| if (cfg->file < 0) { | |
| error_setg(errp, "No IGVM file loaded."); | |
| return -1; | |
| } | |
| ctx.cfg = cfg; | |
| trace_igvm_process_file(cfg->file, onlyVpContext); | |
| ctx.machine_state = machine_state; | |
| /* | |
| * The ConfidentialGuestSupport object is optional and allows a confidential | |
| * guest platform to perform extra processing, such as page measurement, on | |
| * IGVM directives. | |
| */ | |
| ctx.cgsc = machine_state->cgs ? | |
| CONFIDENTIAL_GUEST_SUPPORT_GET_CLASS(machine_state->cgs) : | |
| NULL; | |
| /* | |
| * Check that the IGVM file provides configuration for the current | |
| * platform | |
| */ | |
| if (qigvm_supported_platform_compat_mask(&ctx, errp) < 0) { | |
| goto cleanup; | |
| } | |
| /* | |
| * Process the initialization section first so that the guest policy is | |
| * known before the directive section is handled. The SNP ID block | |
| * directive embeds the guest policy into the ID block, so the policy from | |
| * the guest policy initialization header must be available by then. | |
| */ | |
| header_count = | |
| igvm_header_count(ctx.cfg->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; | |
| } | |
| for (ctx.current_header_index = 0; | |
| ctx.current_header_index < (unsigned)header_count; | |
| ctx.current_header_index++) { | |
| IgvmVariableHeaderType type = | |
| igvm_get_header_type(ctx.cfg->file, | |
| IGVM_HEADER_SECTION_INITIALIZATION, | |
| ctx.current_header_index); | |
| if (qigvm_handler(&ctx, type, errp) < 0) { | |
| goto cleanup; | |
| } | |
| } | |
| header_count = igvm_header_count(ctx.cfg->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 raw_type = igvm_get_header_type( | |
| ctx.cfg->file, IGVM_HEADER_SECTION_DIRECTIVE, | |
| ctx.current_header_index); | |
| if (!onlyVpContext || igvm_vht_type(raw_type) == IGVM_VHT_VP_CONTEXT) { | |
| if (qigvm_handler(&ctx, raw_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; | |
| } | |
| /* | |
| * 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); | |
| cleanup_parameters: | |
| QTAILQ_FOREACH_SAFE(parameter, &ctx.parameter_data, next, tmp) | |
| { | |
| QTAILQ_REMOVE(&ctx.parameter_data, parameter, next); | |
| g_free(parameter->data); | |
| g_free(parameter); | |
| } | |
| g_free(ctx.id_block); | |
| g_free(ctx.id_auth); | |
| cleanup: | |
| return retval; | |
| } | |
| /* | |
| * cleanup any memory regions created by qigvm_prepare_memory() | |
| */ | |
| void qigvm_cleanup_memory(IgvmCfg *cfg) | |
| { | |
| IgvmMemoryRegion *imr, *tmp; | |
| QTAILQ_FOREACH_SAFE(imr, &cfg->memory_regions, next, tmp) | |
| { | |
| trace_qigvm_cleanup_memory(imr->mr->name); | |
| memory_region_del_subregion(get_system_memory(), imr->mr); | |
| vmstate_unregister_ram(imr->mr, NULL); | |
| QTAILQ_REMOVE(&cfg->memory_regions, imr, next); | |
| /* this triggers MemoryRegion cleanup */ | |
| object_unparent(OBJECT(imr->mr)); | |
| g_free(imr); | |
| } | |
| } |