[PATCH v8 16/16] sev: Provide sev_features flags from IGVM VMSA to KVM_SEV_INIT2

Roy Hopkins posted 16 patches 4 months ago
Maintainers: "Philippe Mathieu-Daudé" <philmd@linaro.org>, "Daniel P. Berrangé" <berrange@redhat.com>, Kashyap Chamarthy <kchamart@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>, Marcelo Tosatti <mtosatti@redhat.com>, Richard Henderson <richard.henderson@linaro.org>, Eduardo Habkost <eduardo@habkost.net>, "Michael S. Tsirkin" <mst@redhat.com>, Marcel Apfelbaum <marcel.apfelbaum@gmail.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, Eric Blake <eblake@redhat.com>, Markus Armbruster <armbru@redhat.com>, Zhao Liu <zhao1.liu@intel.com>
There is a newer version of this series
[PATCH v8 16/16] sev: Provide sev_features flags from IGVM VMSA to KVM_SEV_INIT2
Posted by Roy Hopkins 4 months ago
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 on 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.hopkins@randomman.co.uk>
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Stefano Garzarella <sgarzare@redhat.com>
Acked-by: Gerd Hoffman <kraxel@redhat.com>
Tested-by: Stefano Garzarella <sgarzare@redhat.com>
---
 backends/igvm.c           |  17 +++-
 backends/igvm.h           |   2 +-
 hw/i386/pc_piix.c         |   2 +-
 hw/i386/pc_q35.c          |   2 +-
 include/system/igvm-cfg.h |   5 +-
 target/i386/sev.c         | 161 +++++++++++++++++++++++++++++++++-----
 6 files changed, 163 insertions(+), 26 deletions(-)

diff --git a/backends/igvm.c b/backends/igvm.c
index 034f504716..9853ba941e 100644
--- a/backends/igvm.c
+++ b/backends/igvm.c
@@ -869,7 +869,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;
@@ -913,11 +913,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/backends/igvm.h b/backends/igvm.h
index db02ea9165..a4abab043a 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/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index 3184ea1b37..a3285fbc64 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 6990e1c669..cf871cfdad 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/include/system/igvm-cfg.h b/include/system/igvm-cfg.h
index 321b3196f0..944f23a814 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/target/i386/sev.c b/target/i386/sev.c
index 2798fe1c38..df55a25a67 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;
@@ -2417,6 +2509,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__);
@@ -2436,7 +2546,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;
         }
@@ -2493,6 +2604,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;
@@ -2524,6 +2641,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.43.0
Re: [PATCH v8 16/16] sev: Provide sev_features flags from IGVM VMSA to KVM_SEV_INIT2
Posted by Liam Merwick 3 months, 3 weeks ago

On 13/06/2025 16:32, Roy Hopkins wrote:
> 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 on the IGVM_VHT_VP_CONTEXT

s/with on/with only/

> 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.hopkins@randomman.co.uk>
> Acked-by: Michael S. Tsirkin <mst@redhat.com>
> Acked-by: Stefano Garzarella <sgarzare@redhat.com>
> Acked-by: Gerd Hoffman <kraxel@redhat.com>
> Tested-by: Stefano Garzarella <sgarzare@redhat.com>


Reviewed-by: Liam Merwick <liam.merwick@oracle.com>

> ---
>   backends/igvm.c           |  17 +++-
>   backends/igvm.h           |   2 +-
>   hw/i386/pc_piix.c         |   2 +-
>   hw/i386/pc_q35.c          |   2 +-
>   include/system/igvm-cfg.h |   5 +-
>   target/i386/sev.c         | 161 +++++++++++++++++++++++++++++++++-----
>   6 files changed, 163 insertions(+), 26 deletions(-)
> 
> diff --git a/backends/igvm.c b/backends/igvm.c
> index 034f504716..9853ba941e 100644
> --- a/backends/igvm.c
> +++ b/backends/igvm.c
> @@ -869,7 +869,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;
> @@ -913,11 +913,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/backends/igvm.h b/backends/igvm.h
> index db02ea9165..a4abab043a 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/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
> index 3184ea1b37..a3285fbc64 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 6990e1c669..cf871cfdad 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/include/system/igvm-cfg.h b/include/system/igvm-cfg.h
> index 321b3196f0..944f23a814 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/target/i386/sev.c b/target/i386/sev.c
> index 2798fe1c38..df55a25a67 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;
> @@ -2417,6 +2509,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__);
> @@ -2436,7 +2546,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;
>           }
> @@ -2493,6 +2604,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;
> @@ -2524,6 +2641,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);
Re: [PATCH v8 14/16] backends/igvm: Handle policy for SEV guests
Posted by Roy Hopkins 3 months, 1 week ago
> On 13/06/2025 16:22, Roy Hopkins wrote:
> > Adds a handler for the guest policy initialization IGVM section and
> > builds an SEV policy based on this information and the ID block
> > directive if present. The policy is applied using by calling
> > 'set_guest_policy()' on the ConfidentialGuestSupport object.
> > 
> > Signed-off-by: Roy Hopkins <roy.hopkins@randomman.co.uk>
> > Acked-by: Michael S. Tsirkin <mst@redhat.com>
> > Acked-by: Stefano Garzarella <sgarzare@redhat.com>
> > Acked-by: Gerd Hoffman <kraxel@redhat.com>
> > ---
> > backends/igvm.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 138 insertions(+)
> > 
> > diff --git a/backends/igvm.c b/backends/igvm.c
> > index ebdb4594d1..034f504716 100644
> > --- a/backends/igvm.c
> > +++ b/backends/igvm.c
> > @@ -27,6 +27,33 @@ typedef struct QIgvmParameterData {
> > 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 */
> 
> 
> Could mention that the following are in Chapter 8 of
> "SEV Secure Nested Paging Firmware ABI Specification"
> 
Good suggestion. I'll add it.
> 
> > +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];
> > +};
> > +
> 
> In the next patch (#15), sev_snp_id_authentication is in target/i386/sev.h
> Should they be all together? (and sev_snp_id_authentication seems
> identical to sev_id_authentication - do we we need both structs?)
> 
That does make sense and I hoped to do that, but the two files should not really
depend on each other in their current locations. Perhaps we can look at moving
the SEV definitions into a common location in a future patch? 

> > /*
> > * QIgvm contains the information required during processing
> > * of a single IGVM file.
> > @@ -38,6 +65,17 @@ typedef struct QIgvm {
> > 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;
> > @@ -67,6 +105,11 @@ static int qigvm_directive_environment_info(QIgvm *ctx,
> > 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;
> > @@ -91,6 +134,10 @@ static struct QIGVMHandler handlers[] = {
> > 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)
> > @@ -632,6 +679,70 @@ static int qigvm_directive_required_memory(QIgvm *ctx,
> > 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.");
> 
> Error string should be on one line to make it easier to search/grep for.

This, and the other formatting comments are this way due to the formatting
requirements in the project. checkpatch.pl fails with an error if I try
to stretch these lines out.

> 
> > + 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 = 1;
> 
> Worth a #define for version?
> 
Yes. Done.

> > + memcpy(ctx->id_block->ld, igvm_id->ld, sizeof(ctx->id_block->ld));
> > +
> > + ctx->id_auth->id_key_alg = igvm_id->id_key_algorithm;
> > + memcpy(ctx->id_auth->id_block_sig, &igvm_id->id_key_signature,
> > + sizeof(igvm_id->id_key_signature));
> > +
> 
> Should the sizeof be ctx->id_auth->id_block_sig (the dest) ?
> 
> > + ctx->id_auth->auth_key_algo = igvm_id->author_key_algorithm;
> > + memcpy(ctx->id_auth->id_key_sig, &igvm_id->author_key_signature,
> > + sizeof(igvm_id->author_key_signature));
> > +
> 
> Should the sizeof be ctx->id_auth->id_key_sig ?
> 
Unfortunately the two structures are different sizes. I've added an assert
for this and the other one below to catch any future errors.

> > + /*
> > + * 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);
> > +
> 
> For both memcpy calls, they could fit on one line and be more readable.
> 
> > + 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;
> > @@ -701,12 +812,16 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
> > /* 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,
> > @@ -716,6 +831,23 @@ static int qigvm_supported_platform_compat_mask(QIgvm *ctx, Error **errp)
> > 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;
> > @@ -814,12 +946,18 @@ int qigvm_process_file(IgvmCfg *cfg, ConfidentialGuestSupport *cgs,
> > */
> > 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);