On Wed, Oct 26, 2022 at 02:57:33PM +0300, Dov Murik wrote: > (sorry in advance for missing CCs, I tried to download the mbox from > https://listman.redhat.com/archives/libvir-list/ but it doesn't include > the To and Cc lines of the messages.) > > > On 19/10/2022 13:17, berrange at redhat.com (Daniel P. Berrangé) wrote: > > When doing direct kernel boot we need to include the kernel, initrd and > > cmdline in the measurement. > > > > Signed-off-by: Daniel P. Berrang? <berrange at redhat.com> > > --- > > docs/manpages/virt-qemu-sev-validate.rst | 43 ++++++++++ > > tools/virt-qemu-sev-validate | 102 ++++++++++++++++++++++- > > 2 files changed, 144 insertions(+), 1 deletion(-) > > > > diff --git a/docs/manpages/virt-qemu-sev-validate.rst b/docs/manpages/virt-qemu-sev-validate.rst > > index 2c02a27103..da804ae6a0 100644 > > --- a/docs/manpages/virt-qemu-sev-validate.rst > > +++ b/docs/manpages/virt-qemu-sev-validate.rst > > @@ -102,6 +102,20 @@ initialize AMD SEV. For the validation to be trustworthy it important that the > > firmware build used has no support for loading non-volatile variables from > > NVRAM, even if NVRAM is expose to the guest. > > > > +``-k PATH``, ``--kernel=PATH`` > > + > > +Path to the kernel binary if doing direct kernel boot. > > + > > +``-r PATH``, ``--initrd=PATH`` > > + > > +Path to the initrd binary if doing direct kernel boot. Defaults to zero length > > +content if omitted. > > + > > +``-e STRING``, ``--cmdline=STRING`` > > + > > +String containing any kernel command line parameters used during boot of the > > +domain. Defaults to the empty string if omitted. > > + > > ``--tik PATH`` > > > > TIK file for domain. This file must be exactly 16 bytes in size and contains the > > @@ -180,6 +194,22 @@ Validate the measurement of a SEV guest booting from disk: > > --build-id 13 \ > > --policy 3 > > > > +Validate the measurement of a SEV guest with direct kernel boot: > > + > > +:: > > + > > + # virt-dom-sev-validate \ > > + --firmware OVMF.sev.fd \ > > + --kernel vmlinuz-5.11.12 \ > > + --initrd initramfs-5.11.12 \ > > + --cmdline "root=/dev/vda1" \ > > + --tk this-guest-tk.bin \ > > + --measurement Zs2pf19ubFSafpZ2WKkwquXvACx9Wt/BV+eJwQ/taO8jhyIj/F8swFrybR1fZ2ID \ > > + --api-major 0 \ > > + --api-minor 24 \ > > + --build-id 13 \ > > + --policy 3 > > + > > Fetch from remote libvirt > > ------------------------- > > > > @@ -200,6 +230,19 @@ Validate the measurement of a SEV guest booting from disk: > > --tk this-guest-tk.bin \ > > --domain fedora34x86_64 > > > > +Validate the measurement of a SEV guest with direct kernel boot: > > + > > +:: > > + > > + # virt-dom-sev-validate \ > > + --connect qemu+ssh://root at some.remote.host/system \ > > + --firmware OVMF.sev.fd \ > > + --kernel vmlinuz-5.11.12 \ > > + --initrd initramfs-5.11.12 \ > > + --cmdline "root=/dev/vda1" \ > > + --tk this-guest-tk.bin \ > > + --domain fedora34x86_64 > > + > > Fetch from local libvirt > > ------------------------ > > > > diff --git a/tools/virt-qemu-sev-validate b/tools/virt-qemu-sev-validate > > index eb9485c6ed..062f9545f8 100755 > > --- a/tools/virt-qemu-sev-validate > > +++ b/tools/virt-qemu-sev-validate > > @@ -34,6 +34,7 @@ > > # firmware versions with known flaws. > > # > > > > +import abc > > import argparse > > from base64 import b64decode > > from hashlib import sha256 > > @@ -43,6 +44,7 @@ import re > > import socket > > import sys > > import traceback > > +from uuid import UUID > > > > from lxml import etree > > import libvirt > > @@ -70,6 +72,85 @@ class InvalidStateException(Exception): > > pass > > > > > > +class GUIDTable(abc.ABC): > > + GUID_LEN = 16 > > + > > + def __init__(self, guid, lenlen=2): > > + self.guid = guid > > + self.lenlen = lenlen > > + > > + @abc.abstractmethod > > + def entries(self): > > + pass > > + > > + def build_entry(self, guid, payload, lenlen): > > + dummylen = int(0).to_bytes(lenlen, 'little') > > + entry = bytearray(guid + dummylen + payload) > > + > > + lenle = len(entry).to_bytes(lenlen, 'little') > > + entry[self.GUID_LEN:(self.GUID_LEN + lenlen)] = lenle > > + > > + return bytes(entry) > > + > > + def build(self): > > + payload = self.entries() > > + > > + if len(payload) == 0: > > + return bytes([]) > > + > > + dummylen = int(0).to_bytes(self.lenlen, 'little') > > + table = bytearray(self.guid + dummylen + payload) > > + > > + guidlen = len(table).to_bytes(self.lenlen, 'little') > > + table[self.GUID_LEN:(self.GUID_LEN + self.lenlen)] = guidlen > > + > > + pad = 16 - (len(table) % 16) > > + table += bytes([0]) * pad > > + > > + log.debug("Table: %s", bytes(table).hex()) > > + return bytes(table) > > + > > + > > +class KernelTable(GUIDTable): > > + > > + TABLE_GUID = UUID('{9438d606-4f22-4cc9-b479-a793-d411fd21}').bytes_le > > + KERNEL_GUID = UUID('{4de79437-abd2-427f-b835-d5b1-72d2045b}').bytes_le > > + INITRD_GUID = UUID('{44baf731-3a2f-4bd7-9af1-41e2-9169781d}').bytes_le > > + CMDLINE_GUID = UUID('{97d02dd8-bd20-4c94-aa78-e771-4d36ab2a}').bytes_le > > + > > + def __init__(self): > > + super().__init__(guid=self.TABLE_GUID, > > + lenlen=2) > > + > > + self.kernel = None > > + self.initrd = None > > + self.cmdline = None > > + > > + def load_kernel(self, path): > > + with open(path, "rb") as fh: > > + self.kernel = sha256(fh.read()).digest() > > + log.debug("Kernel: %s", self.kernel.hex()) > > + > > + def load_initrd(self, path): > > + with open(path, "rb") as fh: > > + self.initrd = sha256(fh.read()).digest() > > + log.debug("Initrd: %s", self.initrd.hex()) > > + > > + def load_cmdline(self, val): > > + self.cmdline = sha256(val.encode("utf8") + bytes([0])).digest() > > + log.debug("Cmdline: %s", self.cmdline.hex()) > > + > > + def entries(self): > > + entries = bytes([]) > > + if self.cmdline is not None: > > + entries += self.build_entry(self.CMDLINE_GUID, self.cmdline, 2) > > + if self.initrd is not None: > > + entries += self.build_entry(self.INITRD_GUID, self.initrd, 2) > > I think this will not work correctly if cmdline and/or initrd are not > supplied. The QEMU behaviour is to always include all three entries in > the table. > > If initrd is not supplied then its entry should be sha256(""). > > If cmdline is not supplied then its entry should be sha256(bytes[0]). Ah yes, I had known that, but completely forgot to address it before sending. I wonder if we should fully describe the SEV hashes table in the docs/specs/sev-guest-firmware.rst file, to avoid others making the same mistake. > In any case, for direct boot kernel must be supplied. It doesn't make > sense to pass initrd or cmdline without kernel. Good point, will make that combination report an error. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|