[libvirt PATCH v3 06/12] tools: support validating SEV-ES initial vCPU state measurements

Daniel P. Berrangé berrange at redhat.com
Wed Nov 2 11:58:55 UTC 2022


With the SEV-ES policy the VMSA state of each vCPU must be included in
the measured data. The VMSA state can be generated using the 'sevctl'
tool, by telling it a QEMU VMSA is required, and passing the hypevisor's
CPU SKU (family, model, stepping).

Signed-off-by: Daniel P. Berrangé <berrange at redhat.com>
---
 docs/manpages/virt-qemu-sev-validate.rst | 58 ++++++++++++++++++++
 tools/virt-qemu-sev-validate             | 69 ++++++++++++++++++++++--
 2 files changed, 124 insertions(+), 3 deletions(-)

diff --git a/docs/manpages/virt-qemu-sev-validate.rst b/docs/manpages/virt-qemu-sev-validate.rst
index 8fa1452c5c..9f86212cb9 100644
--- a/docs/manpages/virt-qemu-sev-validate.rst
+++ b/docs/manpages/virt-qemu-sev-validate.rst
@@ -116,6 +116,23 @@ content if omitted.
 String containing any kernel command line parameters used during boot of the
 domain. Defaults to the empty string if omitted.
 
+``-n COUNT``, ``--num-cpus=COUNT``
+
+The number of virtual CPUs for the domain. This is required when the
+domain policy is set to require SEV-ES.
+
+``-0 PATH``, ``--vmsa-cpu0=PATH``
+
+Path to the VMSA initial state for the boot CPU. This is required when
+the domain policy is set to require SEV-ES. The file contents must be
+exactly 4096 bytes in length.
+
+``-1 PATH``, ``--vmsa-cpu1=PATH``
+
+Path to the VMSA initial state for the non-boot CPU. This is required when
+the domain policy is set to require SEV-ES and the domain has more than one
+CPU present. The file contents must be exactly 4096 bytes in length.
+
 ``--tik PATH``
 
 TIK file for domain. This file must be exactly 16 bytes in size and contains the
@@ -212,6 +229,22 @@ Validate the measurement of a SEV guest with direct kernel boot:
        --build-id 13 \
        --policy 3
 
+Validate the measurement of a SEV-ES SMP guest booting from disk:
+
+::
+
+   # virt-dom-sev-validate \
+       --firmware OVMF.sev.fd \
+       --num-cpus 2 \
+       --vmsa-cpu0 vmsa0.bin \
+       --vmsa-cpu1 vmsa1.bin \
+       --tk this-guest-tk.bin \
+       --measurement Zs2pf19ubFSafpZ2WKkwquXvACx9Wt/BV+eJwQ/taO8jhyIj/F8swFrybR1fZ2ID \
+       --api-major 0 \
+       --api-minor 24 \
+       --build-id 13 \
+       --policy 7
+
 Fetch from remote libvirt
 -------------------------
 
@@ -245,6 +278,19 @@ Validate the measurement of a SEV guest with direct kernel boot:
        --tk this-guest-tk.bin \
        --domain fedora34x86_64
 
+Validate the measurement of a SEV-ES SMP guest booting from disk:
+
+::
+
+   # virt-dom-sev-validate \
+       --connect qemu+ssh://root@some.remote.host/system \
+       --firmware OVMF.sev.fd \
+       --num-cpus 2 \
+       --vmsa-cpu0 vmsa0.bin \
+       --vmsa-cpu1 vmsa1.bin \
+       --tk this-guest-tk.bin \
+       --domain fedora34x86_64
+
 Fetch from local libvirt
 ------------------------
 
@@ -274,6 +320,18 @@ Validate the measurement of a SEV guest with direct kernel boot:
        --tk this-guest-tk.bin \
        --domain fedora34x86_64
 
+Validate the measurement of a SEV-ES SMP guest booting from disk:
+
+::
+
+   # virt-dom-sev-validate \
+       --insecure \
+       --num-cpus 2 \
+       --vmsa-cpu0 vmsa0.bin \
+       --vmsa-cpu1 vmsa1.bin \
+       --tk this-guest-tk.bin \
+       --domain fedora34x86_64
+
 EXIT STATUS
 ===========
 
diff --git a/tools/virt-qemu-sev-validate b/tools/virt-qemu-sev-validate
index 2c696ba04c..5abcedfb1f 100755
--- a/tools/virt-qemu-sev-validate
+++ b/tools/virt-qemu-sev-validate
@@ -153,13 +153,16 @@ class KernelTable(GUIDTable):
 
 
 class ConfidentialVM(object):
+    POLICY_BIT_SEV_ES = 2
+    POLICY_VAL_SEV_ES = (1 << POLICY_BIT_SEV_ES)
 
     def __init__(self,
                  measurement=None,
                  api_major=None,
                  api_minor=None,
                  build_id=None,
-                 policy=None):
+                 policy=None,
+                 num_cpus=None):
         self.measurement = measurement
         self.api_major = api_major
         self.api_minor = api_minor
@@ -170,8 +173,15 @@ class ConfidentialVM(object):
         self.tik = None
         self.tek = None
 
+        self.num_cpus = num_cpus
+        self.vmsa_cpu0 = None
+        self.vmsa_cpu1 = None
+
         self.kernel_table = KernelTable()
 
+    def is_sev_es(self):
+        return self.policy & self.POLICY_VAL_SEV_ES
+
     def load_tik_tek(self, tik_path, tek_path):
         with open(tik_path, 'rb') as fh:
             self.tik = fh.read()
@@ -207,6 +217,43 @@ class ConfidentialVM(object):
             self.firmware = fh.read()
         log.debug("Loader(sha256): %s", sha256(self.firmware).hexdigest())
 
+    @staticmethod
+    def _load_vmsa(path):
+        with open(path, 'rb') as fh:
+            vmsa = fh.read()
+
+        if len(vmsa) != 4096:
+            raise UnsupportedUsageException(
+                "VMSA must be 4096 bytes in length")
+        return vmsa
+
+    def load_vmsa_cpu0(self, path):
+        self.vmsa_cpu0 = self._load_vmsa(path)
+        log.debug("VMSA CPU 0(sha256): %s",
+                  sha256(self.vmsa_cpu0).hexdigest())
+
+    def load_vmsa_cpu1(self, path):
+        self.vmsa_cpu1 = self._load_vmsa(path)
+        log.debug("VMSA CPU 1(sha256): %s",
+                  sha256(self.vmsa_cpu1).hexdigest())
+
+    def get_cpu_state(self):
+        if self.num_cpus is None:
+            raise UnsupportedUsageException(
+                "Number of virtual CPUs must be specified for SEV-ES domain")
+
+        if self.vmsa_cpu0 is None:
+            raise UnsupportedUsageException(
+                "VMSA for boot CPU required for SEV-ES domain")
+
+        if self.num_cpus > 1 and self.vmsa_cpu1 is None:
+            raise UnsupportedUsageException(
+                "VMSA for additional CPUs required for SEV-ES domain with SMP")
+
+        vmsa = self.vmsa_cpu0 + (self.vmsa_cpu1 * (self.num_cpus - 1))
+        log.debug("VMSA(sha256): %s", sha256(vmsa).hexdigest())
+        return vmsa
+
     # Get the full set of measured launch data for the domain
     #
     # The measured data that the guest is initialized with is the concatenation
@@ -217,6 +264,8 @@ class ConfidentialVM(object):
     def get_measured_data(self):
         measured_data = (self.firmware +
                          self.kernel_table.build())
+        if self.is_sev_es():
+            measured_data += self.get_cpu_state()
         log.debug("Measured-data(sha256): %s",
                   sha256(measured_data).hexdigest())
         return measured_data
@@ -454,6 +503,12 @@ def parse_command_line():
                           help='Path to the initrd binary')
     vmconfig.add_argument('--cmdline', '-e',
                           help='Cmdline string booted with')
+    vmconfig.add_argument('--num-cpus', '-n', type=int,
+                          help='Number of virtual CPUs')
+    vmconfig.add_argument('--vmsa-cpu0', '-0',
+                          help='VMSA state for the boot CPU')
+    vmconfig.add_argument('--vmsa-cpu1', '-1',
+                          help='VMSA state for the additional CPUs')
     vmconfig.add_argument('--tik',
                           help='TIK file for domain')
     vmconfig.add_argument('--tek',
@@ -524,13 +579,15 @@ def attest(args):
                              api_major=args.api_major,
                              api_minor=args.api_minor,
                              build_id=args.build_id,
-                             policy=args.policy)
+                             policy=args.policy,
+                             num_cpus=args.num_cpus)
     else:
         cvm = LibvirtConfidentialVM(measurement=args.measurement,
                                     api_major=args.api_major,
                                     api_minor=args.api_minor,
                                     build_id=args.build_id,
-                                    policy=args.policy)
+                                    policy=args.policy,
+                                    num_cpus=args.num_cpus)
 
     if args.firmware is not None:
         cvm.load_firmware(args.firmware)
@@ -549,6 +606,12 @@ def attest(args):
     if args.cmdline is not None:
         cvm.kernel_table.load_cmdline(args.cmdline)
 
+    if args.vmsa_cpu0 is not None:
+        cvm.load_vmsa_cpu0(args.vmsa_cpu0)
+
+    if args.vmsa_cpu1 is not None:
+        cvm.load_vmsa_cpu1(args.vmsa_cpu1)
+
     if args.domain is not None:
         cvm.load_domain(args.connect,
                         args.domain,
-- 
2.37.3



More information about the libvir-list mailing list