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 firmware build used has no support for loading non-volatile variables from
NVRAM, even if NVRAM is expose to the guest. 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 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
@ -182,6 +196,22 @@ Validate the measurement of a SEV guest booting from disk:
--build-id 13 \ --build-id 13 \
--policy 3 --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 Fetch from remote libvirt
------------------------- -------------------------
@ -202,6 +232,19 @@ Validate the measurement of a SEV guest booting from disk:
--tk this-guest-tk.bin \ --tk this-guest-tk.bin \
--domain fedora34x86_64 --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 Fetch from local libvirt
------------------------ ------------------------

View File

@ -34,6 +34,7 @@
# firmware versions with known flaws. # firmware versions with known flaws.
# #
import abc
import argparse import argparse
from base64 import b64decode from base64 import b64decode
from hashlib import sha256 from hashlib import sha256
@ -43,6 +44,7 @@ import re
import socket import socket
import sys import sys
import traceback import traceback
from uuid import UUID
from lxml import etree from lxml import etree
import libvirt import libvirt
@ -70,6 +72,91 @@ class InvalidStateException(Exception):
pass 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): class ConfidentialVM(object):
def __init__(self, def __init__(self,
@ -88,6 +175,8 @@ class ConfidentialVM(object):
self.tik = None self.tik = None
self.tek = None self.tek = None
self.kernel_table = KernelTable()
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()
@ -129,8 +218,10 @@ class ConfidentialVM(object):
# of the following: # of the following:
# #
# - The firmware blob # - The firmware blob
# - The kernel GUID table
def get_measured_data(self): def get_measured_data(self):
measured_data = self.firmware measured_data = (self.firmware +
self.kernel_table.build())
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
@ -303,6 +394,12 @@ def parse_command_line():
vmconfig = parser.add_argument_group("Virtual machine config") vmconfig = parser.add_argument_group("Virtual machine config")
vmconfig.add_argument('--firmware', '-f', vmconfig.add_argument('--firmware', '-f',
help='Path to the firmware binary') 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', vmconfig.add_argument('--tik',
help='TIK file for domain') help='TIK file for domain')
vmconfig.add_argument('--tek', vmconfig.add_argument('--tek',
@ -361,6 +458,11 @@ def check_usage(args):
raise UnsupportedUsageException( raise UnsupportedUsageException(
"Either --firmware or --domain is required") "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): def attest(args):
if args.domain is None: if args.domain is None:
@ -384,6 +486,15 @@ def attest(args):
else: else:
cvm.load_tik_tek(args.tik, args.tek) 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: if args.domain is not None:
cvm.load_domain(args.connect, cvm.load_domain(args.connect,
args.domain, args.domain,