When doing direct kernel boot we need to include the kernel, initrd and
cmdline in the measurement.
Signed-off-by: Daniel P. Berrangé <berrange(a)redhat.com>
---
docs/manpages/virt-qemu-sev-validate.rst | 43 +++++++++
tools/virt-qemu-sev-validate | 113 ++++++++++++++++++++++-
2 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/docs/manpages/virt-qemu-sev-validate.rst
b/docs/manpages/virt-qemu-sev-validate.rst
index e2c4672a05..e8a868f5a8 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
@@ -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
------------------------
diff --git a/tools/virt-qemu-sev-validate b/tools/virt-qemu-sev-validate
index 31c739c10f..b978c3eb3d 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,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,
--
2.37.3