git: fe019d5e443b - stable/14 - bhyve: TPM 2.0 emulation with swtpm

From: Corvin Köhne <corvink_at_FreeBSD.org>
Date: Tue, 12 Nov 2024 07:55:46 UTC
The branch stable/14 has been updated by corvink:

URL: https://cgit.FreeBSD.org/src/commit/?id=fe019d5e443bdf3b40eec06078d2c596e84f8c56

commit fe019d5e443bdf3b40eec06078d2c596e84f8c56
Author:     Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
AuthorDate: 2024-09-09 08:45:40 +0000
Commit:     Corvin Köhne <corvink@FreeBSD.org>
CommitDate: 2024-11-12 07:54:22 +0000

    bhyve: TPM 2.0 emulation with swtpm
    
    Implement a TPM 2.0 emulation backend to connect to a running swtpm
    instance using a UNIX domain socket.
    
    Reviewed by:            corvink
    MFC after:              1 week
    Differential Revision:  https://reviews.freebsd.org/D46373
    
    (cherry picked from commit d93fbcf0222b05fcd7704b35bc94e5513c2682e2)
---
 usr.sbin/bhyve/Makefile         |   1 +
 usr.sbin/bhyve/bhyve.8          |  26 ++++++--
 usr.sbin/bhyve/bhyve_config.5   |  45 ++++++++++----
 usr.sbin/bhyve/tpm_emul_swtpm.c | 132 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 187 insertions(+), 17 deletions(-)

diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
index 03ea77769754..f4dae2f09904 100644
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -54,6 +54,7 @@ SRCS=	\
 	sockstream.c		\
 	tpm_device.c		\
 	tpm_emul_passthru.c	\
+	tpm_emul_swtpm.c	\
 	tpm_intf_crb.c		\
 	tpm_ppi_qemu.c		\
 	uart_backend.c		\
diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index ffdaf71d1d8a..90f34b3314f8 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -374,7 +374,7 @@ PCI 16550 serial device.
 .It Cm lpc
 LPC PCI-ISA bridge with COM1, COM2, COM3, and COM4 16550 serial ports,
 a boot ROM, and,
-optionally, a fwcfg type and the debug/test device.
+optionally, a TPM module, a fwcfg type, and the debug/test device.
 The LPC bridge emulation can only be configured on bus 0.
 .It Cm fbuf
 Raw framebuffer device attached to VNC server.
@@ -566,10 +566,25 @@ process.
 Use the host TTY device for serial port I/O.
 .El
 .Pp
-TPM device backends:
-.Bl -tag -width 10n
-.It Ar type Ns \&, Ns Ar path Ns Op Cm \&, Ns Ar tpm-device-options
 Emulate a TPM device.
+Supported options for
+.Ar type :
+.Bl -tag -width 10n
+.It Cm passthru
+Use a physical TPM device.
+The argument
+.Ar path
+needs to point to a valid TPM device path, i.e.
+.Pa /dev/tpm0 .
+.It Cm swtpm
+Connect to a running
+.Cm swtpm
+instance.
+The argument
+.Ar path
+needs to point to a UNIX domain socket that a
+.Cm swtpm
+process is listening on.
 .El
 .Pp
 The
@@ -579,7 +594,8 @@ are:
 .It Cm version= Ns Ar version
 Version of the TPM device according to the TCG specification.
 Defaults to
-.Cm 2.0
+.Cm 2.0 ,
+which is the only version currently supported.
 .El
 .Pp
 Boot ROM device backends:
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index cb2de997e63e..38a52b635ec8 100644
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -146,15 +146,6 @@ Specify the keyboard layout name with the file name in
 This value only works when loaded with UEFI mode for VNC, and
 used a VNC client that don't support QEMU Extended Key Event
 Message (e.g. TightVNC).
-.It Va tpm.path Ta string Ta Ta
-Path to the host TPM device.
-This is typically /dev/tpm0.
-.It Va tpm.type Ta string Ta Ta
-Type of the TPM device passed to the guest.
-Currently, only "passthru" is supported.
-.It Va tpm.version Ta string Ta 2.0 Ta
-Version of the TPM device according to the TCG specification.
-Currently, only version 2.0 is supported.
 .It Va rtc.use_localtime Ta bool Ta true Ta
 The real time clock uses the local time of the host.
 If this is set to false, the real time clock uses UTC.
@@ -274,9 +265,8 @@ Intel e82545 network interface.
 VGA framebuffer device attached to VNC server.
 .It Li lpc
 LPC PCI-ISA bridge with COM1-COM4 16550 serial ports,
-a boot ROM,
-an optional fwcfg type,
-and an optional debug/test device.
+a boot ROM, and, optionally, a TPM module, a fwcfg type,
+and a debug/test device.
 This device must be configured on bus 0.
 .It Li hda
 High Definition audio controller.
@@ -593,6 +583,37 @@ This value is required for the Intel GOP driver to work properly.
 .It Va subdevice Ta 0
 .El
 .El
+.Ss TPM Device Settings
+The TPM device stores its configuration under a top-level
+.Va tpm
+node rather than under the LPC TPM device's node.
+Only one TPM device is supported.
+The following nodes are available under
+.Va tpm :
+.Bl -column "tpm.version" "Format" "Default"
+.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
+.It Va tpm.path Ta string Ta Ta
+Path to the TPM backend.
+Depending on the
+.Va tpm.type ,
+this is either the host TPM device, typically
+.Pa /dev/tpm0 ,
+or any UNIX domain socket on which a
+.Cm swtpm
+process is listening.
+.It Va tpm.type Ta string Ta Ta
+Type of the TPM device passed to the guest.
+This can be either
+.Dq passthru
+to use the host TPM devices, or
+.Dq swtpm
+to connect to a running
+.Cm swtpm
+process.
+.It Va tpm.version Ta string Ta 2.0 Ta
+Version of the TPM device according to the TCG specification.
+Currently, only version 2.0 is supported.
+.El
 .Ss NVMe Controller Settings
 Each NVMe controller supports a single storage device.
 The device can be backed either by a memory disk described by the
diff --git a/usr.sbin/bhyve/tpm_emul_swtpm.c b/usr.sbin/bhyve/tpm_emul_swtpm.c
new file mode 100644
index 000000000000..aaa0c9c358cd
--- /dev/null
+++ b/usr.sbin/bhyve/tpm_emul_swtpm.c
@@ -0,0 +1,132 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Hans Rosenfeld
+ * Author: Hans Rosenfeld <rosenfeld@grumpf.hope-2000.org>
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <malloc_np.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "config.h"
+#include "tpm_device.h"
+#include "tpm_emul.h"
+
+struct tpm_swtpm {
+	int fd;
+};
+
+struct tpm_resp_hdr {
+	uint16_t tag;
+	uint32_t len;
+	uint32_t errcode;
+} __packed;
+
+static int
+tpm_swtpm_init(void **sc, nvlist_t *nvl)
+{
+	struct tpm_swtpm *tpm;
+	const char *path;
+	struct sockaddr_un tpm_addr;
+
+	tpm = calloc(1, sizeof (struct tpm_swtpm));
+	if (tpm == NULL) {
+		warnx("%s: failed to allocate tpm_swtpm", __func__);
+		return (ENOMEM);
+	}
+
+	path = get_config_value_node(nvl, "path");
+	if (path == NULL) {
+		warnx("%s: no socket path specified", __func__);
+		return (ENOENT);
+	}
+
+	tpm->fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	if (tpm->fd < 0) {
+		warnx("%s: unable to open tpm socket", __func__);
+		return (ENOENT);
+	}
+
+	bzero(&tpm_addr, sizeof (tpm_addr));
+	tpm_addr.sun_family = AF_UNIX;
+	strlcpy(tpm_addr.sun_path, path, sizeof (tpm_addr.sun_path) - 1);
+
+	if (connect(tpm->fd, (struct sockaddr *)&tpm_addr, sizeof (tpm_addr)) ==
+	    -1) {
+		warnx("%s: unable to connect to tpm socket \"%s\"", __func__,
+		    path);
+		return (ENOENT);
+	}
+
+	*sc = tpm;
+
+	return (0);
+}
+
+static int
+tpm_swtpm_execute_cmd(void *sc, void *cmd, uint32_t cmd_size, void *rsp,
+    uint32_t rsp_size)
+{
+	struct tpm_swtpm *tpm;
+	ssize_t len;
+
+	if (rsp_size < (ssize_t)sizeof(struct tpm_resp_hdr)) {
+		warn("%s: rsp_size of %u is too small", __func__, rsp_size);
+		return (EINVAL);
+	}
+
+	tpm = sc;
+
+	len = send(tpm->fd, cmd, cmd_size, MSG_NOSIGNAL|MSG_DONTWAIT);
+	if (len == -1)
+		err(1, "%s: cmd send failed, is swtpm running?", __func__);
+	if (len != cmd_size) {
+		warn("%s: cmd write failed (bytes written: %zd / %d)", __func__,
+		    len, cmd_size);
+		return (EFAULT);
+	}
+
+	len = recv(tpm->fd, rsp, rsp_size, 0);
+	if (len == -1)
+		err(1, "%s: rsp recv failed, is swtpm running?", __func__);
+	if (len < (ssize_t)sizeof(struct tpm_resp_hdr)) {
+		warn("%s: rsp read failed (bytes read: %zd / %d)", __func__,
+		    len, rsp_size);
+		return (EFAULT);
+	}
+
+	return (0);
+}
+
+static void
+tpm_swtpm_deinit(void *sc)
+{
+	struct tpm_swtpm *tpm;
+
+	tpm = sc;
+	if (tpm == NULL)
+		return;
+
+	if (tpm->fd >= 0)
+		close(tpm->fd);
+
+	free(tpm);
+}
+
+static const struct tpm_emul tpm_emul_swtpm = {
+	.name = "swtpm",
+	.init = tpm_swtpm_init,
+	.deinit = tpm_swtpm_deinit,
+	.execute_cmd = tpm_swtpm_execute_cmd,
+};
+TPM_EMUL_SET(tpm_emul_swtpm);