tools: support validating SEV direct kernel boot measurements

When doing direct kernel boot we need to include the kernel, initrd and
cmdline in the measurement.

Reviewed-by: Cole Robinson <crobinso@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrangé 2022-01-07 16:15:23 +00:00
parent 0e911045ae
commit 0b9e70b141
2 changed files with 155 additions and 1 deletions

View File

@ -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
@ -182,6 +196,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
-------------------------
@ -202,6 +232,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@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
------------------------

View File

@ -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,91 @@ 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(hex): %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()
def load_initrd(self, path):
with open(path, "rb") as fh:
self.initrd = sha256(fh.read()).digest()
def load_cmdline(self, val):
self.cmdline = sha256(val.encode("utf8") + bytes([0])).digest()
def entries(self):
entries = bytes([])
if self.kernel is None:
return entries
if self.initrd is None:
self.initrd = sha256(bytes([])).digest()
if self.cmdline is None:
self.cmdline = sha256(bytes([0])).digest()
log.debug("Kernel(sha256): %s", self.kernel.hex())
log.debug("Initrd(sha256): %s", self.initrd.hex())
log.debug("Cmdline(sha256): %s", self.cmdline.hex())
entries += self.build_entry(self.CMDLINE_GUID, self.cmdline, 2)
entries += self.build_entry(self.INITRD_GUID, self.initrd, 2)
entries += self.build_entry(self.KERNEL_GUID, self.kernel, 2)
return entries
class ConfidentialVM(object):
def __init__(self,
@ -88,6 +175,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 +218,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
@ -303,6 +394,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',
@ -361,6 +458,11 @@ def check_usage(args):
raise UnsupportedUsageException(
"Either --firmware or --domain is required")
if args.kernel is None:
if args.initrd is not None or args.cmdline is not None:
raise UnsupportedUsageException(
"--initrd/--cmdline require --kernel")
def attest(args):
if args.domain is None:
@ -384,6 +486,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,