[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH V9 5/5] Add a TPM Passthrough backend driver impleme
From: |
Stefan Berger |
Subject: |
[Qemu-devel] [PATCH V9 5/5] Add a TPM Passthrough backend driver implementation |
Date: |
Mon, 26 Sep 2011 12:35:14 -0400 |
User-agent: |
quilt/0.48-1 |
>From Andreas Niederl's original posting with adaptations where necessary:
This patch is based of off version 9 of Stefan Berger's patch series
"Qemu Trusted Platform Module (TPM) integration"
and adds a new backend driver for it.
This patch adds a passthrough backend driver for passing commands sent to the
emulated TPM device directly to a TPM device opened on the host machine.
Thus it is possible to use a hardware TPM device in a system running on QEMU,
providing the ability to access a TPM in a special state (e.g. after a Trusted
Boot).
This functionality is being used in the acTvSM Trusted Virtualization Platform
which is available on [1].
Usage example:
qemu-system-x86_64 -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \
-device tpm-tis,tpmdev=tpm0 \
-cdrom test.iso -boot d
Some notes about the host TPM:
The TPM needs to be enabled and activated. If that's not the case one
has to go through the BIOS/UEFI and enable and activate that TPM for TPM
commands to work as expected.
It may be necessary to boot the kernel using tpm_tis.force=1 in the boot
command line or 'modprobe tpm_tis force=1' in case of using it as a module.
Changes for v9:
- prefixing of all functions and variables with tpm_passthrough_
- cleanup of all variables into a structure that is now accessed
using TPMBackend (tb->s.tpm_pt)
- build it on Linux machines
- added function to test whether given device is a TPM and refuse
startup if it is not
Regards,
Andreas Niederl, Stefan Berger
[1] http://trustedjava.sourceforge.net/
Signed-off-by: Andreas Niederl <address@hidden>
Signed-off-by: Stefan Berger <address@hidden>
---
Makefile.target | 1
configure | 3
hw/tpm_passthrough.c | 458 +++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-options.hx | 24 ++
tpm.c | 21 ++
tpm.h | 34 +++
6 files changed, 541 insertions(+)
create mode 100644 hw/tpm_passthrough.c
Index: qemu-git.pt/Makefile.target
===================================================================
--- qemu-git.pt.orig/Makefile.target
+++ qemu-git.pt/Makefile.target
@@ -195,6 +195,7 @@ obj-$(CONFIG_KVM) += kvm.o kvm-all.o
obj-$(CONFIG_NO_KVM) += kvm-stub.o
obj-y += memory.o
obj-$(CONFIG_TPM) += tpm.o tpm_tis.o
+obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
LIBS+=-lz
QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
Index: qemu-git.pt/configure
===================================================================
--- qemu-git.pt.orig/configure
+++ qemu-git.pt/configure
@@ -3565,6 +3565,9 @@ fi
if test "$tpm" = "yes"; then
if test "$target_softmmu" = "yes" ; then
+ if test "$linux" = "yes" ; then
+ echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_target_mak
+ fi
echo "CONFIG_TPM=y" >> $config_host_mak
fi
fi
Index: qemu-git.pt/hw/tpm_passthrough.c
===================================================================
--- /dev/null
+++ qemu-git.pt/hw/tpm_passthrough.c
@@ -0,0 +1,458 @@
+/*
+ * passthrough TPM driver
+ *
+ * Copyright (c) 2010, 2011 IBM Corporation
+ * Authors:
+ * Stefan Berger <address@hidden>
+ *
+ * Copyright (C) 2011 IAIK, Graz University of Technology
+ * Author: Andreas Niederl
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu-common.h"
+#include "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+/* #define DEBUG_TPM */
+
+/* data structures */
+
+typedef struct ThreadParams {
+ TPMState *tpm_state;
+
+ TPMRecvDataCB *recv_data_callback;
+ TPMBackend *tb;
+} ThreadParams;
+
+struct TPMPassthruState {
+ QemuThread thread;
+ bool thread_terminate;
+ bool thread_running;
+
+ ThreadParams tpm_thread_params;
+
+ char tpm_dev[64];
+ int tpm_fd;
+ bool had_startup_error;
+};
+
+/* borrowed from qemu-char.c */
+static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
+{
+ int ret, len1;
+
+ len1 = len;
+ while (len1 > 0) {
+ ret = write(fd, buf, len1);
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else if (ret == 0) {
+ break;
+ } else {
+ buf += ret;
+ len1 -= ret;
+ }
+ }
+ return len - len1;
+}
+
+static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
+{
+ int ret, len1;
+ uint8_t *buf1;
+
+ len1 = len;
+ buf1 = buf;
+ while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) {
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else {
+ buf1 += ret;
+ len1 -= ret;
+ }
+ }
+ return len - len1;
+}
+
+static void *tpm_passthrough_main_loop(void *d)
+{
+ ThreadParams *thr_parms = d;
+ TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
+ uint32_t in_len, out_len;
+ uint8_t *in, *out;
+ uint8_t locty;
+ TPMLocality *cmd_locty;
+ int ret;
+
+#ifdef DEBUG_TPM
+ fprintf(stderr, "tpm_passthrough: THREAD IS STARTING\n");
+#endif
+
+ /* start command processing */
+ while (!tpm_pt->thread_terminate) {
+ /* receive and handle commands */
+ in_len = 0;
+ do {
+#ifdef DEBUG_TPM
+ fprintf(stderr, "tpm_passthrough: waiting for commands...\n");
+#endif
+
+ if (tpm_pt->thread_terminate) {
+ break;
+ }
+
+ qemu_mutex_lock(&thr_parms->tpm_state->state_lock);
+
+ /* in case we were to slow and missed the signal, the
+ to_tpm_execute boolean tells us about a pending command */
+ if (!thr_parms->tpm_state->to_tpm_execute) {
+ qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond,
+ &thr_parms->tpm_state->state_lock);
+ }
+
+ thr_parms->tpm_state->to_tpm_execute = false;
+
+ qemu_mutex_unlock(&thr_parms->tpm_state->state_lock);
+
+ if (tpm_pt->thread_terminate) {
+ break;
+ }
+
+ locty = thr_parms->tpm_state->command_locty;
+
+ cmd_locty = thr_parms->tpm_state->cmd_locty;
+
+ in = cmd_locty->w_buffer.buffer;
+ in_len = cmd_locty->w_offset;
+ out = cmd_locty->r_buffer.buffer;
+ out_len = cmd_locty->r_buffer.size;
+
+ ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
+ if (ret < 0) {
+ fprintf(stderr,
+ "tpm_passthrough: error while transmitting data "
+ "to host tpm: %s (%i)\n",
+ strerror(errno), errno);
+ tpm_write_std_fatal_error_response(out, out_len, in, in_len);
+ goto send_resp;
+ }
+
+ ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
+ if (ret < 0) {
+ fprintf(stderr,
+ "tpm_passthrough: error while reading data from host "
+ "tpm : %s (%i)\n",
+ strerror(errno), errno);
+ tpm_write_std_fatal_error_response(out, out_len, in, in_len);
+ }
+
+send_resp:
+ thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
+ } while (in_len > 0);
+ }
+
+#ifdef DEBUG_TPM
+ fprintf(stderr, "tpm_passthrough: THREAD IS ENDING\n");
+#endif
+
+ tpm_pt->thread_running = false;
+
+ return NULL;
+}
+
+static void tpm_passthrough_terminate_tpm_thread(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ if (!tpm_pt->thread_running) {
+ return;
+ }
+
+#if defined DEBUG_TPM
+ fprintf(stderr, "tpm_passthrough: TERMINATING RUNNING TPM THREAD\n");
+#endif
+
+ if (!tpm_pt->thread_terminate) {
+ tpm_pt->thread_terminate = true;
+
+ qemu_mutex_lock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+ qemu_cond_signal(&tpm_pt->tpm_thread_params.tpm_state->to_tpm_cond);
+ qemu_mutex_unlock(&tpm_pt->tpm_thread_params.tpm_state->state_lock);
+
+ while (tpm_pt->thread_running) {
+ usleep(100000);
+ }
+ memset(&tpm_pt->thread, 0, sizeof(tpm_pt->thread));
+ }
+}
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_do_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ /* terminate a running TPM */
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ /* reset the flag so the thread keeps on running */
+ tpm_pt->thread_terminate = false;
+
+ qemu_thread_create(&tpm_pt->thread, tpm_passthrough_main_loop,
+ &tpm_pt->tpm_thread_params);
+
+ tpm_pt->thread_running = true;
+
+ return 0;
+}
+
+static int tpm_passthrough_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+ int rc;
+
+ rc = tpm_passthrough_do_startup_tpm(tb);
+ if (rc) {
+ tpm_pt->had_startup_error = true;
+ }
+ return rc;
+}
+
+static void tpm_passthrough_reset(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+#if defined DEBUG_TPM
+ fprintf(stderr, "tpm_passthrough: CALL TO TPM_RESET!\n");
+#endif
+
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ tpm_pt->had_startup_error = false;
+}
+
+static int tpm_passthrough_init(TPMBackend *tb,
+ TPMState *s, TPMRecvDataCB *recv_data_cb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_pt->tpm_thread_params.tpm_state = s;
+ tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
+ tpm_pt->tpm_thread_params.tb = tb;
+
+ tpm_pt->thread_running = false;
+
+ return 0;
+}
+
+static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
+{
+ return false;
+}
+
+static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ return tpm_pt->had_startup_error;
+}
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+ size_t wanted_size = 4096;
+
+ if (sb->size != wanted_size) {
+ sb->buffer = g_realloc(sb->buffer, wanted_size);
+ if (sb->buffer != NULL) {
+ sb->size = wanted_size;
+ } else {
+ sb->size = 0;
+ }
+ }
+ return sb->size;
+}
+
+static const char *tpm_passthrough_create_desc(void)
+{
+ return "Passthrough TPM backend driver";
+}
+
+/* A basic test of a TPM device. We expect a well formatted response header
+ * (error response is fine) within one second.
+ */
+static int tpm_passthrough_test_tpmdev(int fd)
+{
+ struct tpm_req_hdr req = {
+ .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
+ .len = cpu_to_be32(sizeof(req)),
+ .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
+ };
+ struct tpm_resp_hdr *resp;
+ fd_set readfds;
+ int n;
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ unsigned char buf[1024];
+
+ n = write(fd, &req, sizeof(req));
+ if (n < 0) {
+ return errno;
+ }
+ if (n != sizeof(req)) {
+ return EFAULT;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+
+ /* wait for a second */
+ n = select(fd + 1, &readfds, NULL, NULL, &tv);
+ if (n != 1) {
+ return errno;
+ }
+
+ n = read(fd, &buf, sizeof(buf));
+ if (n < sizeof(struct tpm_resp_hdr)) {
+ return EFAULT;
+ }
+
+ resp = (struct tpm_resp_hdr *)buf;
+ /* check the header */
+ if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
+ be32_to_cpu(resp->len) != n) {
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
+ const char *model)
+{
+ TPMBackend *tb;
+ const char *value;
+ char buf[64];
+ int n;
+
+ tb = g_malloc(sizeof(TPMBackend));
+ if (tb == NULL) {
+ fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
+ return NULL;
+ }
+
+ tb->s.tpm_pt = g_malloc(sizeof(TPMPassthruState));
+ if (tb->s.tpm_pt == NULL) {
+ fprintf(stderr, "tpm_passthrough: Could not allocate memory.\n");
+ g_free(tb);
+ return NULL;
+ }
+
+ tb->id = g_strdup(id);
+ tb->model = NULL;
+ if (model) {
+ tb->model = g_strdup(model);
+ }
+ tb->ops = &tpm_passthrough_driver;
+
+ value = qemu_opt_get(opts, "path");
+ if (value) {
+ n = snprintf(tb->s.tpm_pt->tpm_dev, sizeof(tb->s.tpm_pt->tpm_dev),
+ "%s", value);
+
+ if (n >= sizeof(tb->s.tpm_pt->tpm_dev)) {
+ fprintf(stderr, "TPM device path is too long.\n");
+ goto err_exit;
+ }
+
+ snprintf(buf, sizeof(buf), "path=%s", tb->s.tpm_pt->tpm_dev);
+
+ tb->parameters = g_strdup(buf);
+
+ if (tb->parameters == NULL) {
+ goto err_exit;
+ }
+
+ tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+ if (tb->s.tpm_pt->tpm_fd < 0) {
+ fprintf(stderr,
+ "Cannot open device '%s' from TPM's path option.\n",
+ tb->s.tpm_pt->tpm_dev);
+ goto err_exit;
+ }
+
+ } else {
+ fprintf(stderr, "-tpmdev is missing path= parameter\n");
+ goto err_exit;
+ }
+
+ if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+ fprintf(stderr,
+ "'%s' is not a TPM device.\n",
+ tb->s.tpm_pt->tpm_dev);
+ goto err_close_tpmdev;
+ }
+
+ return tb;
+
+err_close_tpmdev:
+ close(tb->s.tpm_pt->tpm_fd);
+
+err_exit:
+ g_free(tb->id);
+ g_free(tb->model);
+ g_free(tb->parameters);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+ return NULL;
+}
+
+static void tpm_passthrough_destroy(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_passthrough_terminate_tpm_thread(tb);
+
+ close(tpm_pt->tpm_fd);
+
+ g_free(tb->id);
+ g_free(tb->model);
+ g_free(tb->parameters);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+}
+
+const TPMDriverOps tpm_passthrough_driver = {
+ .id = "passthrough",
+ .desc = tpm_passthrough_create_desc,
+ .create = tpm_passthrough_create,
+ .destroy = tpm_passthrough_destroy,
+ .init = tpm_passthrough_init,
+ .startup_tpm = tpm_passthrough_startup_tpm,
+ .realloc_buffer = tpm_passthrough_realloc_buffer,
+ .reset = tpm_passthrough_reset,
+ .had_startup_error = tpm_passthrough_get_startup_error,
+ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+};
Index: qemu-git.pt/qemu-options.hx
===================================================================
--- qemu-git.pt.orig/qemu-options.hx
+++ qemu-git.pt/qemu-options.hx
@@ -1777,6 +1777,7 @@ The general form of a TPM device option
@item -tpmdev @var{backend} ,address@hidden [,@var{options}]
@findex -tpmdev
Backend type must be:
address@hidden
The specific backend type will determine the applicable options.
The @code{-tpmdev} options requires a @code{-device} option.
@@ -1788,6 +1789,29 @@ Use ? to print all available TPM backend
qemu -tpmdev ?
@end example
address@hidden -tpmdev passthrough, address@hidden, address@hidden
+
+Enables access to the host's TPM using the passthrough driver.
+
address@hidden specifies the path to the host's TPM device, i.e., on
+a Linux host this would be @code{/dev/tpm0}.
+
+Note that the passthrough device must not be used by any application on
+the host. Since the host's firmware has already initialized the TPM,
+the firmware (BIOS) executed by QEMU will not be able to initialize the
+TPM again and behave differently than if it could initialize the TPM.
+If TPM ownership is released from within a QEMU VM then this requires
+rebooting of the host and entering the host's firmware to enable and activate
+the TPM again.
address@hidden is required.
+
+To create a passthrough TPM use the following two options:
address@hidden
+-tpmdev pasthrough,id=tpm0,path=<path to TPM device> -device
tpm-tis,tpmdev=tpm0
address@hidden example
+Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
address@hidden in the device option.
+
@end table
ETEXI
Index: qemu-git.pt/tpm.c
===================================================================
--- qemu-git.pt.orig/tpm.c
+++ qemu-git.pt/tpm.c
@@ -18,12 +18,33 @@
#include "qerror.h"
static const TPMDriverOps *bes[] = {
+ &tpm_passthrough_driver,
NULL,
};
static QLIST_HEAD(, TPMBackend) tpm_backends =
QLIST_HEAD_INITIALIZER(tpm_backends);
+void tpm_write_std_fatal_error_response(uint8_t *out, uint32_t out_len,
+ uint8_t *in, uint32_t in_len)
+{
+ if (out_len > sizeof(struct tpm_resp_hdr)) {
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+ struct tpm_req_hdr *req = (struct tpm_req_hdr *)in;
+ uint16_t tag = be16_to_cpu(req->tag);
+
+ resp->tag = cpu_to_be16(
+ (in_len > 2 &&
+ tag >= TPM_TAG_RQU_COMMAND &&
+ tag <= TPM_TAG_RQU_AUTH2_COMMAND)
+ ? tag + 3
+ : TPM_TAG_RSP_COMMAND);
+
+ resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
+ resp->errcode = cpu_to_be32(TPM_FAIL);
+ }
+}
+
const TPMDriverOps *tpm_get_backend_driver(const char *id)
{
int i;
Index: qemu-git.pt/tpm.h
===================================================================
--- qemu-git.pt.orig/tpm.h
+++ qemu-git.pt/tpm.h
@@ -6,12 +6,18 @@
struct TPMDriverOps;
typedef struct TPMDriverOps TPMDriverOps;
+typedef struct TPMPassthruState TPMPassthruState;
+
typedef struct TPMBackend {
char *id;
char *model;
char *parameters;
const TPMDriverOps *ops;
+ union {
+ TPMPassthruState *tpm_pt;
+ } s;
+
QLIST_ENTRY(TPMBackend) list;
} TPMBackend;
@@ -77,6 +83,30 @@ static inline void tpm_dump_buffer(FILE
#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
+struct tpm_req_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t ordinal;
+} __attribute__((packed));
+
+struct tpm_resp_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t errcode;
+} __attribute__((packed));
+
+#define TPM_TAG_RQU_COMMAND 0xc1
+#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
+#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
+
+#define TPM_TAG_RSP_COMMAND 0xc4
+#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
+#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
+
+#define TPM_FAIL 9
+
+#define TPM_ORD_GetTicks 0xf1
+
void tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
int tpm_init(void);
void tpm_cleanup(void);
@@ -84,5 +114,9 @@ TPMBackend *qemu_find_tpm(const char *id
void do_info_tpm(Monitor *mon);
void tpm_display_backend_drivers(FILE *out);
const TPMDriverOps *tpm_get_backend_driver(const char *id);
+void tpm_write_std_fatal_error_response(uint8_t *out, uint32_t out_len,
+ uint8_t *in, uint32_t in_len);
+
+extern const TPMDriverOps tpm_passthrough_driver;
#endif /* _QEMU_TPM_H */
[Qemu-devel] [PATCH V9 4/5] Build the TPM frontend code, Stefan Berger, 2011/09/26
Re: [Qemu-devel] [PATCH V9 0/5] Qemu Trusted Platform Module (TPM) integration, Michael S. Tsirkin, 2011/09/26