[libvirt PATCH v2 04/12] tools: support validating SEV direct kernel boot measurements

Dov Murik dovmurik at linux.ibm.com
Wed Oct 26 11:57:33 UTC 2022


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


In any case, for direct boot kernel must be supplied.  It doesn't make
sense to pass initrd or cmdline without kernel.

-Dov


> +        if self.kernel is not None:
> +            entries += self.build_entry(self.KERNEL_GUID, self.kernel, 2)
> +        return entries
> +
> +
>  class ConfidentialVM(object):
>  
>      def __init__(self,
> @@ -88,6 +169,8 @@ class ConfidentialVM(object):
>          self.tik = None
>          self.tek = None
>  
> +        self.kernel_table = KernelTable()
> +
>      def load_tik_tek(self, tik_path, tek_path):
>          with open(tik_path, 'rb') as fh:
>              self.tik = fh.read()
> @@ -129,8 +212,10 @@ class ConfidentialVM(object):
>      # of the following:
>      #
>      #  - The firmware blob
> +    #  - The kernel GUID table
>      def get_measured_data(self):
> -        measured_data = self.firmware
> +        measured_data = (self.firmware +
> +                         self.kernel_table.build())
>          log.debug("Measured-data(sha256): %s",
>                    sha256(measured_data).hexdigest())
>          return measured_data
> @@ -298,6 +383,12 @@ def parse_command_line():
>      vmconfig = parser.add_argument_group("Virtual machine config")
>      vmconfig.add_argument('--firmware', '-f',
>                            help='Path to the firmware binary')
> +    vmconfig.add_argument('--kernel', '-k',
> +                          help='Path to the kernel binary')
> +    vmconfig.add_argument('--initrd', '-r',
> +                          help='Path to the initrd binary')
> +    vmconfig.add_argument('--cmdline', '-e',
> +                          help='Cmdline string booted with')
>      vmconfig.add_argument('--tik',
>                            help='TIK file for domain')
>      vmconfig.add_argument('--tek',
> @@ -379,6 +470,15 @@ def attest(args):
>      else:
>          cvm.load_tik_tek(args.tik, args.tek)
>  
> +    if args.kernel is not None:
> +        cvm.kernel_table.load_kernel(args.kernel)
> +
> +    if args.initrd is not None:
> +        cvm.kernel_table.load_initrd(args.initrd)
> +
> +    if args.cmdline is not None:
> +        cvm.kernel_table.load_cmdline(args.cmdline)
> +
>      if args.domain is not None:
>          cvm.load_domain(args.connect,
>                          args.domain,



More information about the libvir-list mailing list