Skip to content

Commit d9539c5

Browse files
committed
Add confidential guest support: Secure Execution support
Example to start a confidential guest using two host-key documents ``` $ vng -r --confidential-guest --confidential-guest-args host-key-document=/home/mhartmay/storage/git/hostkeys/a46/HKD-3931-02772A8.crt --confidential-guest-args host-key-document=/home/mhartmay/storage/git/hostkeys/b35/HKD-9175-029DE48.crt ``` Another example where always the given `pvimg create` arguments are used. To do so, modify the `default_opts` sections in `~/.config/virtme-ng/virtme-ng.conf` as following: ``` { "default_opts": { "confidential_guest_args": ["host-key-document=/home/user/HKD.crt"] }, } ``` Now you can simply run `vng --confidential-guest` to prepare the Secure Execution boot image using the given host-key document. Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
1 parent 094ddac commit d9539c5

4 files changed

Lines changed: 253 additions & 4 deletions

File tree

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,61 @@ Examples
525525
$ sudo vng --nvgpu "01:00.0" -r linux
526526
```
527527

528+
- Run virtme-ng with confidential guest support (currently only s390x support). **Use this only for test purposes!**
529+
530+
For [IBM Secure
531+
Execution](https://www.ibm.com/docs/en/linux-on-systems?topic=management-secure-execution)
532+
the guest boot image/kernel needs to be prepared using at least one so-called
533+
host-key document. This document can be passed to the `pvimg create` command
534+
by means of the `--confidential-guest-args` option.
535+
536+
```
537+
vng --confidential-guest --confidential_guest_args 'host-key-document=/home/user/HKD.crt'
538+
```
539+
540+
As this host-key document does not change that frequently, it makes sense to
541+
make use of [`virtme-ng.conf`](#configuration). To do so, modify the
542+
`default_opts` sections in `~/.config/virtme-ng/virtme-ng.conf` as following:
543+
544+
```
545+
{
546+
"default_opts": {
547+
"confidential_guest_args": ["host-key-document=/home/user/HKD.crt"]
548+
},
549+
}
550+
```
551+
552+
Now you can simply run `vng --confidential-guest` to prepare the boot image
553+
and start a confidential guest using this newly created boot image.
554+
555+
+ Run virtme-ng with confidential dump support (currently only s390x support)
556+
557+
```
558+
# Specify a confidential dump encryption key, e.g. via the virtme-ng.conf
559+
{
560+
"default_opts": {
561+
"confidential_guest_args": ["host-key-document=/home/user/HKD.crt", "cck=/home/user/cck"]
562+
}
563+
564+
# Start the vng instance in debug mode
565+
$ vng --confidential-guest --debug
566+
567+
# In a separate shell session trigger the memory dump to /tmp/vmcore.img
568+
$ vng --dump /tmp/vmcore.img
569+
570+
# Decrypt the dump on-the-fly (fusermount3 is needed)
571+
$ zgetdump --mount /mnt/ --key /home/user/cck /tmp/vmcore.img
572+
573+
# Use drgn to read 'jiffies' from the memory dump:
574+
$ echo "print(prog['jiffies'])" | drgn -q -s vmlinux -c /mnt/dump.elf
575+
drgn 0.0.23 (using Python 3.11.6, elfutils 0.189, with libkdumpfile)
576+
For help, type help(drgn).
577+
>>> import drgn
578+
>>> from drgn import NULL, Object, cast, container_of, execscript, offsetof, reinterpret, sizeof
579+
>>> from drgn.helpers.common import *
580+
>>> from drgn.helpers.linux import *
581+
```
582+
528583
Implementation details
529584
======================
530585

virtme/architectures.py

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@
55
# as a file called LICENSE with SHA-256 hash:
66
# 8177f97513213526df2cf6184d8ff986c675afb514d4e68a404010521b880643
77

8+
import functools
89
import os
9-
from typing import List, Optional
10+
import shlex
11+
import shutil
12+
import subprocess
13+
import sys
14+
import tempfile
15+
from pathlib import Path
16+
from typing import Any, List, Optional
1017

1118

1219
class Arch:
@@ -71,6 +78,25 @@ def qemu_sound_args() -> List[str]:
7178
def qemu_vmcoreinfo_args() -> List[str]:
7279
return ["-device", "vmcoreinfo"]
7380

81+
@staticmethod
82+
def qemu_confidential_guest_args(
83+
is_native: bool, use_kvm: bool, **kwargs
84+
) -> List[str]:
85+
raise RuntimeError("Confidential guest is not supported by the architecture")
86+
87+
# pylint: disable=unused-argument
88+
def qemu_confidential_guest_img_preparation(
89+
self,
90+
*,
91+
kernel: Path,
92+
cmdline: str,
93+
initrd: Optional[Path] = None,
94+
dry_run: bool = False,
95+
verbose: bool = False,
96+
**kwargs,
97+
) -> Optional[Path]:
98+
return None
99+
74100
@staticmethod
75101
def qemu_serial_console_args() -> List[str]:
76102
# We should be using the new-style -device serialdev,chardev=xyz,
@@ -387,11 +413,24 @@ def qemu_nodisplay_args():
387413

388414

389415
class Arch_s390x(Arch):
416+
PV_DUMP_SUPPORT = 0x8000000000
417+
390418
def __init__(self):
391419
Arch.__init__(self, "s390x")
392420

393421
self.linuxname = "s390"
394422

423+
@staticmethod
424+
def test_uv_support(value: int) -> bool:
425+
with open("/sys/firmware/uv/query/facilities", "rb") as f:
426+
data = f.read().decode()
427+
428+
inst_call_list = data.splitlines()
429+
assert len(inst_call_list) == 4
430+
inst_call_list_int = list(map(functools.partial(int, base=16), inst_call_list))
431+
first_inst_call = inst_call_list_int[0]
432+
return first_inst_call & value
433+
395434
@staticmethod
396435
def virtiofs_support() -> bool:
397436
return True
@@ -451,6 +490,85 @@ def earlyconsole_args() -> List[str]:
451490
def qemu_vmcoreinfo_args() -> List[str]:
452491
return []
453492

493+
@staticmethod
494+
def qemu_confidential_guest_args(
495+
is_native: bool, use_kvm: bool, **kwargs
496+
) -> List[str]:
497+
try:
498+
with open("/sys/firmware/uv/prot_virt_host", "rb") as f:
499+
data = f.read().decode()
500+
except FileNotFoundError:
501+
return RuntimeError(
502+
"Secure Execution is not supported by your kernel/hardware."
503+
)
504+
if int(data) != 1:
505+
return RuntimeError(
506+
"Secure Execution is not supported on your system. 'prot_virt=1' missing?"
507+
)
508+
if not is_native or not use_kvm:
509+
return RuntimeError("KVM must be used for Secure Execution")
510+
511+
return [
512+
"-object",
513+
"s390-pv-guest,id=pv0",
514+
"-machine",
515+
"confidential-guest-support=pv0",
516+
]
517+
518+
def qemu_confidential_guest_img_preparation(
519+
self,
520+
*,
521+
kernel: Path,
522+
cmdline: str,
523+
initrd: Optional[Path] = None,
524+
dry_run: bool = False,
525+
verbose: bool = False,
526+
**kwargs,
527+
) -> Optional[Any]:
528+
if "host-key-document" not in kwargs:
529+
raise RuntimeError(
530+
"virtme-run: At least one host-key-document=$HKD must be specified"
531+
)
532+
if not shutil.which("pvimg"):
533+
raise RuntimeError(
534+
"'pvimg' must be installed for the Secure Execution guest image preparation"
535+
)
536+
537+
confidential_guest_args = [
538+
f"--{key}={value}" for key, values in kwargs.items() for value in values
539+
]
540+
541+
output = tempfile.NamedTemporaryFile(suffix="seimg")
542+
if self.test_uv_support(self.PV_DUMP_SUPPORT) and "cck" in kwargs:
543+
if verbose:
544+
sys.stderr.write(f"virtme: using CCK in '{kwargs.get('cck')[-1]}'\n")
545+
confidential_guest_args += ["--enable-dump"]
546+
prepare_cmd = [
547+
"pvimg",
548+
"create",
549+
f"--kernel={kernel}",
550+
f"--output={output.name}",
551+
"--no-verify",
552+
"--overwrite",
553+
] + confidential_guest_args
554+
if initrd is not None:
555+
prepare_cmd.append(f"--ramdisk={initrd}")
556+
with tempfile.NamedTemporaryFile() as f:
557+
f.write(cmdline.encode("ascii"))
558+
f.flush()
559+
prepare_cmd.append(f"--parmfile={f.name}")
560+
prepare_cmd_str = shlex.join(prepare_cmd)
561+
if dry_run:
562+
print(prepare_cmd_str)
563+
else:
564+
subprocess.check_call(
565+
prepare_cmd,
566+
stderr=subprocess.PIPE,
567+
text=True,
568+
close_fds=False,
569+
)
570+
return output
571+
454572
def img_name(self) -> List[str]:
455573
return ["vmlinuz", "image"]
456574

virtme/commands/run.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@
3939
from ..util import SilentError, find_binary_or_raise, get_username
4040

4141

42+
class kwargsAppendAction(argparse.Action):
43+
def __call__(self, parser, namespace, pairs, option_string=None):
44+
assert len(pairs) > 0
45+
for pair in pairs:
46+
try:
47+
key, value = pair.split("=")
48+
except ValueError as error:
49+
raise argparse.ArgumentError(
50+
self, f'could not parse argument "{pair}" as key=value format'
51+
) from error
52+
d = getattr(namespace, self.dest) or {}
53+
values = d.get(key, [])
54+
values.append(value)
55+
d[key] = values
56+
setattr(namespace, self.dest, d)
57+
58+
4259
def make_parser() -> argparse.ArgumentParser:
4360
parser = argparse.ArgumentParser(
4461
description="Virtualize your system (or another) under a kernel image",
@@ -299,6 +316,18 @@ def make_parser() -> argparse.ArgumentParser:
299316
action="store",
300317
help="Save the generated initramfs to the specified path",
301318
)
319+
g.add_argument(
320+
"--confidential-guest",
321+
action="store_true",
322+
help="Prepare image for confidential guest",
323+
)
324+
g.add_argument(
325+
"--confidential-guest-args",
326+
nargs="+",
327+
action=kwargsAppendAction,
328+
metavar="KEY=VALUE",
329+
help="Add key/value params. May appear multiple times.",
330+
)
302331
g.add_argument(
303332
"--show-boot-console",
304333
action="store_true",
@@ -1888,8 +1917,28 @@ def get_net_mac(index):
18881917
# sure that 'init=' appears directly before '--'.
18891918
kernelargs.extend(initcmds)
18901919

1891-
# Load a normal kernel
1892-
qemuargs.extend(["-kernel", kernel.kimg])
1920+
kernel_cmdline = " ".join(quote_karg(a) for a in kernelargs)
1921+
if args.confidential_guest:
1922+
qemuargs.extend(
1923+
arch.qemu_confidential_guest_args(is_native=is_native, use_kvm=kvm_ok)
1924+
)
1925+
output = arch.qemu_confidential_guest_img_preparation(
1926+
kernel=kernel.kimg,
1927+
cmdline=kernel_cmdline,
1928+
initrd=initrdpath,
1929+
dry_run=args.dry_run,
1930+
verbose=args.verbose,
1931+
**args.confidential_guest_args,
1932+
)
1933+
if output is not None:
1934+
kimg = output.name
1935+
else:
1936+
kimg = kernel.kimg
1937+
else:
1938+
kimg = kernel.kimg
1939+
1940+
# Load the kernel
1941+
qemuargs.extend(["-kernel", kimg])
18931942
if kernelargs:
18941943
if args.systemd:
18951944
init_environment_vars = []
@@ -1913,7 +1962,7 @@ def get_net_mac(index):
19131962
f.write(f"\nExecStart={guest_tools_path}/{virtme_init_cmd}")
19141963
else:
19151964
f.write(f"\nExecStart=/run/virtme/guesttools/{virtme_init_cmd}")
1916-
qemuargs.extend(["-append", " ".join(quote_karg(a) for a in kernelargs)])
1965+
qemuargs.extend(["-append", kernel_cmdline])
19171966
if initrdpath is not None:
19181967
qemuargs.extend(["-initrd", initrdpath])
19191968
if kernel.dtb is not None:

virtme_ng/run.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,18 @@ def make_parser():
410410
action="store_true",
411411
help="Use an initramfs even if unnecessary",
412412
)
413+
parser.add_argument(
414+
"--confidential-guest",
415+
action="store_true",
416+
help="Prepare image for confidential guest",
417+
)
418+
parser.add_argument(
419+
"--confidential-guest-args",
420+
action="extend",
421+
nargs="+",
422+
type=str,
423+
help="Confidential guest keyword arguments",
424+
)
413425

414426
parser.add_argument(
415427
"--sound",
@@ -1271,6 +1283,19 @@ def _get_virtme_nvgpu(self, args):
12711283
else:
12721284
self.virtme_param["nvgpu"] = ""
12731285

1286+
def _get_virtme_confidential_guest(self, args):
1287+
if args.confidential_guest:
1288+
if args.confidential_guest_args is None:
1289+
arg_fail(
1290+
"error: --confidential-guest can be used only with --confidential-guest-args",
1291+
show_usage=False,
1292+
)
1293+
self.virtme_param["confidential_guest"] = (
1294+
f"--confidential-guest --confidential-guest-args {shlex.join(args.confidential_guest_args)}"
1295+
)
1296+
else:
1297+
self.virtme_param["confidential_guest"] = ""
1298+
12741299
def _get_virtme_qemu_opts(self, args):
12751300
qemu_args = ""
12761301
if args.debug:
@@ -1310,6 +1335,7 @@ def run(self, args):
13101335
self._get_virtme_disk(args)
13111336
self._get_virtme_sound(args)
13121337
self._get_virtme_vmcoreinfo(args)
1338+
self._get_virtme_confidential_guest(args)
13131339
self._get_virtme_disable_microvm(args)
13141340
self._get_virtme_disable_monitor(args)
13151341
self._get_virtme_disable_kvm(args)
@@ -1364,6 +1390,7 @@ def run(self, args):
13641390
+ f"{self.virtme_param['disable_kvm']} "
13651391
+ f"{self.virtme_param['ssh_tcp']} "
13661392
+ f"{self.virtme_param['force_9p']} "
1393+
+ f"{self.virtme_param['confidential_guest']} "
13671394
+ f"{self.virtme_param['force_initramfs']} "
13681395
+ f"{self.virtme_param['graphics']} "
13691396
+ f"{self.virtme_param['verbose']} "

0 commit comments

Comments
 (0)