qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH V9 5/5] Add a TPM Passthrough backend driver imp


From: Stefan Berger
Subject: Re: [Qemu-devel] [PATCH V9 5/5] Add a TPM Passthrough backend driver implementation
Date: Mon, 26 Sep 2011 16:12:19 -0400
User-agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.18) Gecko/20110621 Fedora/3.1.11-1.fc14 Lightning/1.0b3pre Thunderbird/3.1.11

On 09/26/2011 03:20 PM, Michael S. Tsirkin wrote:
On Mon, Sep 26, 2011 at 12:35:14PM -0400, Stefan Berger wrote:
> 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;
TPMPassthruThreadParams?
Yes... Fixed.
+
+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");
error_report?
Will use it.
+            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;
+    }
+
It's usually a good idea to allow passing the fd through
a unix file descriptor or command line. net has some code
for that, that can be generalized. Good for security
separation. It does not have to be part of this patch though, just
thinking aloud.

I also wanted to have that but would rather put that in another patch. Basically the following code from net.c needs to go into a separate function:

        char *endptr = NULL;

        fd = strtol(param, &endptr, 10);
        if (*endptr || (fd == 0 && param == endptr)) {
            return -1;
        }

My proposal would be to put it into

int qemu_parse_fd(const char *)

into qemu-char.c ?


+    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;
Is this a must? Is it common to have more than one
tpm device available on a computer? Maybe there's
a good default in case only one tpm exists there ...

Well, passing /dev/tty48 in the place of /dev/tpm0 ends up in a disappointment. So I'd rather check what that device is and refuse to start if it is found not to be a TPM.

+    }
+
+    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}.
So how about making this the default on linux?
Has passthough code any chance to work on non-linux btw?

For sure not on Win32. For other Unices I don't know.
+
+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.
For the benefit of users, could this text clarify what happens with
e.g. linux and windows guests in this case?


I'll try to clarify.

FYI: The pending SeaBIOS patches would typically display a menu if they succeed in sending a (standard) initialization sequence to the TPM. But in this case the SeaBIOS patches aren't needed yet and if used will not succeed in sending that command sequence since the BIOS of the host already initialized the device.
+If TPM ownership is released from within a QEMU VM
When does this happen?

tpm_clearown is a command line tool provided by the TrouSerS tss implementation in the tpm-tools package that allows you to release ownership of the device. If the VM user clears ownership (using the TPM's password), the device will become deactivated and disabled.
then this requires
What requires?

+rebooting of the host and entering the host's firmware
entering?

Go into the menu of the host's firmware (BIOS/UEFI).
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
Note?

:-/
address@hidden in the device option.
+
  @end table

  ETEXI
Thanks for the review.

   Stefan




reply via email to

[Prev in Thread] Current Thread [Next in Thread]