mirror of https://gitee.com/openkylin/libvirt.git
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:
parent
0e911045ae
commit
0b9e70b141
|
@ -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
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue