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: Michael S. Tsirkin
Subject: Re: [Qemu-devel] [PATCH V9 5/5] Add a TPM Passthrough backend driver implementation
Date: Mon, 26 Sep 2011 22:20:19 +0300
User-agent: Mutt/1.5.21 (2010-09-15)

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?

> +
> +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?

> +            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.


> +    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 ...

> +    }
> +
> +    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?

> +
> +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?


> +If TPM ownership is released from within a QEMU VM

When does this happen?

> then this requires

What requires?

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

entering?

> 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



reply via email to

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