From: Roy Hopkins <roy.hopk...@randomman.co.uk>

IGVM files can contain an initial VMSA that should be applied to each
vcpu as part of the initial guest state. The sev_features flags are
provided as part of the VMSA structure. However, KVM only allows
sev_features to be set during initialization and not as the guest is
being prepared for launch.

This patch queries KVM for the supported set of sev_features flags and
processes the VP context entries in the IGVM file during kvm_init to
determine any sev_features flags set in the IGVM file. These are then
provided in the call to KVM_SEV_INIT2 to ensure the guest state
matches that specified in the IGVM file.

The igvm process() function is modified to allow a partial processing
of the file during initialization, with only the IGVM_VHT_VP_CONTEXT
fields being processed. This means the function is called twice,
firstly to extract the sev_features then secondly to actually
configure the guest.

Signed-off-by: Roy Hopkins <roy.hopk...@randomman.co.uk>
Acked-by: Michael S. Tsirkin <m...@redhat.com>
Acked-by: Stefano Garzarella <sgarz...@redhat.com>
Acked-by: Gerd Hoffman <kra...@redhat.com>
Tested-by: Stefano Garzarella <sgarz...@redhat.com>
Reviewed-by: Liam Merwick <liam.merw...@oracle.com>
Reviewed-by: Ani Sinha <anisi...@redhat.com>
Link: 
https://lore.kernel.org/r/b2f986aae04e1da2aee530c9be22a54c0c59a560.1751554099.git.roy.hopk...@randomman.co.uk
Signed-off-by: Paolo Bonzini <pbonz...@redhat.com>
---
 backends/igvm.h           |   2 +-
 include/system/igvm-cfg.h |   5 +-
 backends/igvm.c           |  17 +++-
 hw/i386/pc_piix.c         |   2 +-
 hw/i386/pc_q35.c          |   2 +-
 target/i386/sev.c         | 161 +++++++++++++++++++++++++++++++++-----
 6 files changed, 163 insertions(+), 26 deletions(-)

diff --git a/backends/igvm.h b/backends/igvm.h
index db02ea91651..a4abab043a1 100644
--- a/backends/igvm.h
+++ b/backends/igvm.h
@@ -17,6 +17,6 @@
 #include "qapi/error.h"
 
 int qigvm_process_file(IgvmCfg *igvm, ConfidentialGuestSupport *cgs,
-                      Error **errp);
+                      bool onlyVpContext, Error **errp);
 
 #endif
diff --git a/include/system/igvm-cfg.h b/include/system/igvm-cfg.h
index 321b3196f09..944f23a814d 100644
--- a/include/system/igvm-cfg.h
+++ b/include/system/igvm-cfg.h
@@ -31,11 +31,14 @@ typedef struct IgvmCfgClass {
     /*
      * If an IGVM filename has been specified then process the IGVM file.
      * Performs a no-op if no filename has been specified.
+     * If onlyVpContext is true then only the IGVM_VHT_VP_CONTEXT entries
+     * in the IGVM file will be processed, allowing information about the
+     * CPU state to be determined before processing the entire file.
      *
      * Returns 0 for ok and -1 on error.
      */
     int (*process)(IgvmCfg *cfg, ConfidentialGuestSupport *cgs,
-                   Error **errp);
+                   bool onlyVpContext, Error **errp);
 
 } IgvmCfgClass;
 
diff --git a/backends/igvm.c b/backends/igvm.c
index b568f06c769..9ad41582ee5 100644
--- a/backends/igvm.c
+++ b/backends/igvm.c
@@ -880,7 +880,7 @@ static IgvmHandle qigvm_file_init(char *filename, Error 
**errp)
 }
 
 int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs,
-                       Error **errp)
+                       bool onlyVpContext, Error **errp)
 {
     int32_t header_count;
     QIgvmParameterData *parameter;
@@ -924,11 +924,22 @@ int qigvm_process_file(IgvmCfg *cfg, 
ConfidentialGuestSupport *cgs,
          ctx.current_header_index++) {
         IgvmVariableHeaderType type = igvm_get_header_type(
             ctx.file, IGVM_HEADER_SECTION_DIRECTIVE, ctx.current_header_index);
-        if (qigvm_handler(&ctx, type, errp) < 0) {
-            goto cleanup_parameters;
+        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) {
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index 3184ea1b378..a3285fbc645 100644
--- a/hw/i386/pc_piix.c
+++ b/hw/i386/pc_piix.c
@@ -371,7 +371,7 @@ static void pc_init1(MachineState *machine, const char 
*pci_type)
     /* Apply guest state from IGVM if supplied */
     if (x86ms->igvm) {
         if (IGVM_CFG_GET_CLASS(x86ms->igvm)
-                ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) {
+                ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) 
{
             g_assert_not_reached();
         }
     }
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
index 6990e1c6695..cf871cfdad8 100644
--- a/hw/i386/pc_q35.c
+++ b/hw/i386/pc_q35.c
@@ -330,7 +330,7 @@ static void pc_q35_init(MachineState *machine)
     /* Apply guest state from IGVM if supplied */
     if (x86ms->igvm) {
         if (IGVM_CFG_GET_CLASS(x86ms->igvm)
-                ->process(x86ms->igvm, machine->cgs, &error_fatal) < 0) {
+                ->process(x86ms->igvm, machine->cgs, false, &error_fatal) < 0) 
{
             g_assert_not_reached();
         }
     }
diff --git a/target/i386/sev.c b/target/i386/sev.c
index 3e5722ba657..1057b8ab2c6 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -118,6 +118,8 @@ struct SevCommonState {
     uint32_t cbitpos;
     uint32_t reduced_phys_bits;
     bool kernel_hashes;
+    uint64_t sev_features;
+    uint64_t supported_sev_features;
 
     /* runtime state */
     uint8_t api_major;
@@ -485,7 +487,40 @@ static void sev_apply_cpu_context(CPUState *cpu)
     }
 }
 
-static int check_vmsa_supported(hwaddr gpa, const struct sev_es_save_area 
*vmsa,
+static int check_sev_features(SevCommonState *sev_common, uint64_t 
sev_features,
+                              Error **errp)
+{
+    /*
+     * Ensure SEV_FEATURES is configured for correct SEV hardware and that
+     * the requested features are supported. If SEV-SNP is enabled then
+     * that feature must be enabled, otherwise it must be cleared.
+     */
+    if (sev_snp_enabled() && !(sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) {
+        error_setg(
+            errp,
+            "%s: SEV_SNP is enabled but is not enabled in VMSA sev_features",
+            __func__);
+        return -1;
+    } else if (!sev_snp_enabled() &&
+               (sev_features & SVM_SEV_FEAT_SNP_ACTIVE)) {
+        error_setg(
+            errp,
+            "%s: SEV_SNP is not enabled but is enabled in VMSA sev_features",
+            __func__);
+        return -1;
+    }
+    if (sev_features & ~sev_common->supported_sev_features) {
+        error_setg(errp,
+                   "%s: VMSA contains unsupported sev_features: %lX, "
+                   "supported features: %lX",
+                   __func__, sev_features, sev_common->supported_sev_features);
+        return -1;
+    }
+    return 0;
+}
+
+static int check_vmsa_supported(SevCommonState *sev_common, hwaddr gpa,
+                                const struct sev_es_save_area *vmsa,
                                 Error **errp)
 {
     struct sev_es_save_area vmsa_check;
@@ -551,24 +586,10 @@ static int check_vmsa_supported(hwaddr gpa, const struct 
sev_es_save_area *vmsa,
     vmsa_check.x87_fcw = 0;
     vmsa_check.mxcsr = 0;
 
-    if (sev_snp_enabled()) {
-        if (vmsa_check.sev_features != SVM_SEV_FEAT_SNP_ACTIVE) {
-            error_setg(errp,
-                       "%s: sev_features in the VMSA contains an unsupported "
-                       "value. For SEV-SNP, sev_features must be set to %x.",
-                       __func__, SVM_SEV_FEAT_SNP_ACTIVE);
-            return -1;
-        }
-        vmsa_check.sev_features = 0;
-    } else {
-        if (vmsa_check.sev_features != 0) {
-            error_setg(errp,
-                       "%s: sev_features in the VMSA contains an unsupported "
-                       "value. For SEV-ES and SEV, sev_features must be "
-                       "set to 0.", __func__);
-            return -1;
-        }
+    if (check_sev_features(sev_common, vmsa_check.sev_features, errp) < 0) {
+        return -1;
     }
+    vmsa_check.sev_features = 0;
 
     if (!buffer_is_zero(&vmsa_check, sizeof(vmsa_check))) {
         error_setg(errp,
@@ -1722,6 +1743,39 @@ static int sev_snp_kvm_type(X86ConfidentialGuest *cg)
     return KVM_X86_SNP_VM;
 }
 
+static int sev_init_supported_features(ConfidentialGuestSupport *cgs,
+                                       SevCommonState *sev_common, Error 
**errp)
+{
+    X86ConfidentialGuestClass *x86_klass =
+                               X86_CONFIDENTIAL_GUEST_GET_CLASS(cgs);
+    /*
+     * Older kernels do not support query or setting of sev_features. In this
+     * case the set of supported features must be zero to match the settings
+     * in the kernel.
+     */
+    if (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common)) ==
+        KVM_X86_DEFAULT_VM) {
+        sev_common->supported_sev_features = 0;
+        return 0;
+    }
+
+    /* Query KVM for the supported set of sev_features */
+    struct kvm_device_attr attr = {
+        .group = KVM_X86_GRP_SEV,
+        .attr = KVM_X86_SEV_VMSA_FEATURES,
+        .addr = (unsigned long)&sev_common->supported_sev_features,
+    };
+    if (kvm_ioctl(kvm_state, KVM_GET_DEVICE_ATTR, &attr) < 0) {
+        error_setg(errp, "%s: failed to query supported sev_features",
+                   __func__);
+        return -1;
+    }
+    if (sev_snp_enabled()) {
+        sev_common->supported_sev_features |= SVM_SEV_FEAT_SNP_ACTIVE;
+    }
+    return 0;
+}
+
 static int sev_common_kvm_init(ConfidentialGuestSupport *cgs, Error **errp)
 {
     char *devname;
@@ -1802,6 +1856,10 @@ static int sev_common_kvm_init(ConfidentialGuestSupport 
*cgs, Error **errp)
         }
     }
 
+    if (sev_init_supported_features(cgs, sev_common, errp) < 0) {
+        return -1;
+    }
+
     trace_kvm_sev_init();
     switch (x86_klass->kvm_type(X86_CONFIDENTIAL_GUEST(sev_common))) {
     case KVM_X86_DEFAULT_VM:
@@ -1813,6 +1871,40 @@ static int sev_common_kvm_init(ConfidentialGuestSupport 
*cgs, Error **errp)
     case KVM_X86_SEV_ES_VM:
     case KVM_X86_SNP_VM: {
         struct kvm_sev_init args = { 0 };
+        MachineState *machine = MACHINE(qdev_get_machine());
+        X86MachineState *x86machine = X86_MACHINE(qdev_get_machine());
+
+        /*
+         * If configuration is provided via an IGVM file then the IGVM file
+         * might contain configuration of the initial vcpu context. For SEV
+         * the vcpu context includes the sev_features which should be applied
+         * to the vcpu.
+         *
+         * KVM does not synchronize sev_features from CPU state. Instead it
+         * requires sev_features to be provided as part of this initialization
+         * call which is subsequently automatically applied to the VMSA of
+         * each vcpu.
+         *
+         * The IGVM file is normally processed after initialization. Therefore
+         * we need to pre-process it here to extract sev_features in order to
+         * provide it to KVM_SEV_INIT2. Each cgs_* function that is called by
+         * the IGVM processor detects this pre-process by observing the state
+         * as SEV_STATE_UNINIT.
+         */
+        if (x86machine->igvm) {
+            if (IGVM_CFG_GET_CLASS(x86machine->igvm)
+                    ->process(x86machine->igvm, machine->cgs, true, errp) ==
+                -1) {
+                return -1;
+            }
+            /*
+             * KVM maintains a bitmask of allowed sev_features. This does not
+             * include SVM_SEV_FEAT_SNP_ACTIVE which is set accordingly by KVM
+             * itself. Therefore we need to clear this flag.
+             */
+            args.vmsa_features = sev_common->sev_features &
+                                 ~SVM_SEV_FEAT_SNP_ACTIVE;
+        }
 
         ret = sev_ioctl(sev_common->sev_fd, KVM_SEV_INIT2, &args, &fw_error);
         break;
@@ -2416,6 +2508,24 @@ static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, 
uint64_t len,
     SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs);
     SevCommonStateClass *klass = SEV_COMMON_GET_CLASS(sev_common);
 
+    if (sev_common->state == SEV_STATE_UNINIT) {
+        /* Pre-processing of IGVM file called from sev_common_kvm_init() */
+        if ((cpu_index == 0) && (memory_type == CGS_PAGE_TYPE_VMSA)) {
+            const struct sev_es_save_area *sa =
+                (const struct sev_es_save_area *)ptr;
+            if (len < sizeof(*sa)) {
+                error_setg(errp, "%s: invalid VMSA length encountered",
+                           __func__);
+                return -1;
+            }
+            if (check_sev_features(sev_common, sa->sev_features, errp) < 0) {
+                return -1;
+            }
+            sev_common->sev_features = sa->sev_features;
+        }
+        return 0;
+    }
+
     if (!sev_enabled()) {
         error_setg(errp, "%s: attempt to configure guest memory, but SEV "
                      "is not enabled", __func__);
@@ -2435,7 +2545,8 @@ static int cgs_set_guest_state(hwaddr gpa, uint8_t *ptr, 
uint64_t len,
                        __func__);
             return -1;
         }
-        if (check_vmsa_supported(gpa, (const struct sev_es_save_area *)ptr,
+        if (check_vmsa_supported(sev_common, gpa,
+                                 (const struct sev_es_save_area *)ptr,
                                  errp) < 0) {
             return -1;
         }
@@ -2492,6 +2603,12 @@ static int cgs_get_mem_map_entry(int index,
     struct e820_entry *table;
     int num_entries;
 
+    SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs);
+    if (sev_common->state == SEV_STATE_UNINIT) {
+        /* Pre-processing of IGVM file called from sev_common_kvm_init() */
+        return 1;
+    }
+
     num_entries = e820_get_table(&table);
     if ((index < 0) || (index >= num_entries)) {
         return 1;
@@ -2523,6 +2640,12 @@ static int 
cgs_set_guest_policy(ConfidentialGuestPolicyType policy_type,
                                 uint32_t policy_data1_size, void *policy_data2,
                                 uint32_t policy_data2_size, Error **errp)
 {
+    SevCommonState *sev_common = SEV_COMMON(MACHINE(qdev_get_machine())->cgs);
+    if (sev_common->state == SEV_STATE_UNINIT) {
+        /* Pre-processing of IGVM file called from sev_common_kvm_init() */
+        return 0;
+    }
+
     if (policy_type != GUEST_POLICY_SEV) {
         error_setg(errp, "%s: Invalid guest policy type provided for SEV: %d",
         __func__, policy_type);
-- 
2.50.0


Reply via email to