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(a)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 beb40383be..24bca98d28 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
@@ -210,6 +227,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
-------------------------
@@ -243,6 +276,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
------------------------
@@ -272,6 +318,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 3c2c670689..a88d7dfc01 100755
--- a/tools/virt-qemu-sev-validate
+++ b/tools/virt-qemu-sev-validate
@@ -152,13 +152,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
@@ -169,8 +172,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()
@@ -206,6 +216,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
@@ -216,6 +263,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
@@ -448,6 +497,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',
@@ -513,13 +568,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)
@@ -538,6 +595,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