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@xxxxxxxxxx> --- 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@xxxxxxxxxxxxxxxx/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