tools: support validating SEV-ES initial vCPU state measurements

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).

Reviewed-by: Cole Robinson <crobinso@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2022-01-07 17:25:23 +00:00
parent 7d55c815c6
commit 3e7b7da9e0
2 changed files with 124 additions and 3 deletions

View File

@ -116,6 +116,23 @@ content if omitted.
String containing any kernel command line parameters used during boot of the String containing any kernel command line parameters used during boot of the
domain. Defaults to the empty string if omitted. 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 PATH``
TIK file for domain. This file must be exactly 16 bytes in size and contains the 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 \ --build-id 13 \
--policy 3 --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 Fetch from remote libvirt
------------------------- -------------------------
@ -245,6 +278,19 @@ Validate the measurement of a SEV guest with direct kernel boot:
--tk this-guest-tk.bin \ --tk this-guest-tk.bin \
--domain fedora34x86_64 --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 Fetch from local libvirt
------------------------ ------------------------
@ -274,6 +320,18 @@ Validate the measurement of a SEV guest with direct kernel boot:
--tk this-guest-tk.bin \ --tk this-guest-tk.bin \
--domain fedora34x86_64 --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 EXIT STATUS
=========== ===========

View File

@ -158,13 +158,16 @@ class KernelTable(GUIDTable):
class ConfidentialVM(object): class ConfidentialVM(object):
POLICY_BIT_SEV_ES = 2
POLICY_VAL_SEV_ES = (1 << POLICY_BIT_SEV_ES)
def __init__(self, def __init__(self,
measurement=None, measurement=None,
api_major=None, api_major=None,
api_minor=None, api_minor=None,
build_id=None, build_id=None,
policy=None): policy=None,
num_cpus=None):
self.measurement = measurement self.measurement = measurement
self.api_major = api_major self.api_major = api_major
self.api_minor = api_minor self.api_minor = api_minor
@ -175,8 +178,15 @@ class ConfidentialVM(object):
self.tik = None self.tik = None
self.tek = None self.tek = None
self.num_cpus = num_cpus
self.vmsa_cpu0 = None
self.vmsa_cpu1 = None
self.kernel_table = KernelTable() 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): def load_tik_tek(self, tik_path, tek_path):
with open(tik_path, 'rb') as fh: with open(tik_path, 'rb') as fh:
self.tik = fh.read() self.tik = fh.read()
@ -212,6 +222,43 @@ class ConfidentialVM(object):
self.firmware = fh.read() self.firmware = fh.read()
log.debug("Firmware(sha256): %s", sha256(self.firmware).hexdigest()) log.debug("Firmware(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 # Get the full set of measured launch data for the domain
# #
# The measured data that the guest is initialized with is the concatenation # The measured data that the guest is initialized with is the concatenation
@ -222,6 +269,8 @@ class ConfidentialVM(object):
def get_measured_data(self): def get_measured_data(self):
measured_data = (self.firmware + measured_data = (self.firmware +
self.kernel_table.build()) self.kernel_table.build())
if self.is_sev_es():
measured_data += self.get_cpu_state()
log.debug("Measured-data(sha256): %s", log.debug("Measured-data(sha256): %s",
sha256(measured_data).hexdigest()) sha256(measured_data).hexdigest())
return measured_data return measured_data
@ -459,6 +508,12 @@ def parse_command_line():
help='Path to the initrd binary') help='Path to the initrd binary')
vmconfig.add_argument('--cmdline', '-e', vmconfig.add_argument('--cmdline', '-e',
help='Cmdline string booted with') 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', vmconfig.add_argument('--tik',
help='TIK file for domain') help='TIK file for domain')
vmconfig.add_argument('--tek', vmconfig.add_argument('--tek',
@ -529,13 +584,15 @@ def attest(args):
api_major=args.api_major, api_major=args.api_major,
api_minor=args.api_minor, api_minor=args.api_minor,
build_id=args.build_id, build_id=args.build_id,
policy=args.policy) policy=args.policy,
num_cpus=args.num_cpus)
else: else:
cvm = LibvirtConfidentialVM(measurement=args.measurement, cvm = LibvirtConfidentialVM(measurement=args.measurement,
api_major=args.api_major, api_major=args.api_major,
api_minor=args.api_minor, api_minor=args.api_minor,
build_id=args.build_id, build_id=args.build_id,
policy=args.policy) policy=args.policy,
num_cpus=args.num_cpus)
if args.firmware is not None: if args.firmware is not None:
cvm.load_firmware(args.firmware) cvm.load_firmware(args.firmware)
@ -554,6 +611,12 @@ def attest(args):
if args.cmdline is not None: if args.cmdline is not None:
cvm.kernel_table.load_cmdline(args.cmdline) 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: if args.domain is not None:
cvm.load_domain(args.connect, cvm.load_domain(args.connect,
args.domain, args.domain,