Patchwork [V10,5/5] Add a TPM Passthrough backend driver implementation

login
register
mail settings
Submitter Stefan Berger
Date Sept. 27, 2011, 2:50 p.m.
Message ID <20110927145201.790361002@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/116621/
State New
Headers show

Comments

Stefan Berger - Sept. 27, 2011, 2:50 p.m.
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 v10:
 - clarified documentation
 - using /dev/tpm0 as default device if path option is not given
 - refactored code handling 'device' option into its own function
 - fixed name of structure to TPMPassthruThreadParams
 - only add tpm_passthrough_driver to collection of backends if
   it is compiled on the host

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 <andreas.niederl@iaik.tugraz.at>
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 Makefile.target      |    1 
 configure            |    3 
 hw/tpm_passthrough.c |  471 +++++++++++++++++++++++++++++++++++++++++++++++++++
 qemu-options.hx      |   29 +++
 tpm.c                |   23 ++
 tpm.h                |   34 +++
 6 files changed, 561 insertions(+)
 create mode 100644 hw/tpm_passthrough.c
Michael S. Tsirkin - Sept. 27, 2011, 5:13 p.m.
On Tue, Sep 27, 2011 at 10:50:48AM -0400, Stefan Berger wrote:
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM.

> +Also, if TPM ownership is released from within a VM then this will
> +require a reboot of the host and the user will have to enter the host's
> +firmware menu to enable and activate the TPM again.

Rewrite:
 Further, if TPM ownership is released from within a VM,
 TPM gets deactivated in host.
 To enable and activate the TPM again afterwards,
 host has to be rebooted and the user is required to
 enter the host's firmware menu.

> If the TPM is left
> +disabled and deactivated most TPM commands will fail.

Why do we allow guest to do this then?
Can we return an error, or ignore the release
command? If someone really wants this unsafe behaviour
we could make this an option, off by default.
Stefan Berger - Sept. 27, 2011, 5:38 p.m.
On 09/27/2011 01:13 PM, Michael S. Tsirkin wrote:
> On Tue, Sep 27, 2011 at 10:50:48AM -0400, Stefan Berger wrote:
>> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
>> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
>> +TPM again and may therefore not show a TPM-specific menu that would
>> +otherwise allow the user to configure the TPM.
>> +Also, if TPM ownership is released from within a VM then this will
>> +require a reboot of the host and the user will have to enter the host's
>> +firmware menu to enable and activate the TPM again.
> Rewrite:
>   Further, if TPM ownership is released from within a VM,
>   TPM gets deactivated in host.
Further, if TPM ownership is release from within a VM, the host's TPM 
gets disabled and deactivate.
>   To enable and activate the TPM again afterwards,
>   host has to be rebooted and the user is required to
>   enter the host's firmware menu.
>
>> If the TPM is left
>> +disabled and deactivated most TPM commands will fail.
> Why do we allow guest to do this then?
You cannot prevent it. This is due to how the TPM works. We cannot 
intercept the commands, either, and I don't think it makes much sense. I 
wrote this part of the docs to make people aware of these scenarios so 
they don't come as a surprise.
> Can we return an error, or ignore the release
> command? If someone really wants this unsafe behaviour
> we could make this an option, off by default.
>

In effect you'd have to parse every command that goes from the VM to the 
host and intercept it in the passthrough driver. I don't want to prevent 
it since it's a valid usage scenario but it has side effects (host needs 
to be rebooted). Even if we were to intercept this command then the user 
always has the possibility to send commands in an encrypted form (using 
TPM's transport 'tunnel') where one couldn't intercept this particular 
command anymore. So, my suggestion is we leave it as it is but we make 
people aware of these scenarios.

    Stefan
Michael S. Tsirkin - Sept. 27, 2011, 6:07 p.m.
On Tue, Sep 27, 2011 at 01:38:52PM -0400, Stefan Berger wrote:
> On 09/27/2011 01:13 PM, Michael S. Tsirkin wrote:
> >On Tue, Sep 27, 2011 at 10:50:48AM -0400, Stefan Berger wrote:
> >>+Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> >>+the VM's firmware (BIOS/UEFI) will not be able to initialize the
> >>+TPM again and may therefore not show a TPM-specific menu that would
> >>+otherwise allow the user to configure the TPM.

So what happens is guest tries to init tpm, this fails,
and then guest keeps going and as luck would have it
it can actually operate tpm fine?

> >>+Also, if TPM ownership is released from within a VM then this will
> >>+require a reboot of the host and the user will have to enter the host's
> >>+firmware menu to enable and activate the TPM again.
> >Rewrite:
> >  Further, if TPM ownership is released from within a VM,
> >  TPM gets deactivated in host.
> Further, if TPM ownership is release from within a VM, the host's
> TPM gets disabled and deactivate.

is release->is released
deactivate -> deactivated?

> >  To enable and activate the TPM again afterwards,
> >  host has to be rebooted and the user is required to
> >  enter the host's firmware menu.
> >
> >>If the TPM is left
> >>+disabled and deactivated most TPM commands will fail.
> >Why do we allow guest to do this then?
> You cannot prevent it. This is due to how the TPM works. We cannot
> intercept the commands, either,

Hmm, they go from guest to qemu to host, no?

> and I don't think it makes much
> sense. I wrote this part of the docs to make people aware of these
> scenarios so they don't come as a surprise.
> >Can we return an error, or ignore the release
> >command? If someone really wants this unsafe behaviour
> >we could make this an option, off by default.
> >
> 
> In effect you'd have to parse every command that goes from the VM to
> the host and intercept it in the passthrough driver. I don't want to
> prevent it since it's a valid usage scenario

That's what I'm asking. Why is this valid and useful?

> but it has side effects
> (host needs to be rebooted). Even if we were to intercept this
> command then the user always has the possibility to send commands in
> an encrypted form (using TPM's transport 'tunnel') where one
> couldn't intercept this particular command anymore. So, my
> suggestion is we leave it as it is but we make people aware of these
> scenarios.
> 
>    Stefan

This looks like a major limitation.
Stefan Berger - Sept. 27, 2011, 6:53 p.m.
On 09/27/2011 02:07 PM, Michael S. Tsirkin wrote:
> On Tue, Sep 27, 2011 at 01:38:52PM -0400, Stefan Berger wrote:
>> On 09/27/2011 01:13 PM, Michael S. Tsirkin wrote:
>>> On Tue, Sep 27, 2011 at 10:50:48AM -0400, Stefan Berger wrote:
>>>> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
>>>> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
>>>> +TPM again and may therefore not show a TPM-specific menu that would
>>>> +otherwise allow the user to configure the TPM.
> So what happens is guest tries to init tpm, this fails,
> and then guest keeps going and as luck would have it
> it can actually operate tpm fine?
What happens is that the SeaBIOS extensions will try to initialize the 
host's TPM as if it was the libtpms-based TPM using the same standard 
initialization sequence that is used for every TPM. However, the host's 
TPM cannot be initialized again (host's BIOS did this already) and thus 
SeaBIOS assumes that it is talking to a faulty device and at least will 
not assume it has an operable TPM and *not* send any more commands to 
it. However, the TPM does not shut down because of this attempt to 
re-initialize but continues to operate. SeaBIOS will then also not show 
the TPM menu where the user has the choice of several commands to for 
example activate or enable a TPM or clear ownership. All these commands 
would require 'physical presence' to be activated which cannot be done 
after the host's BIOS has previously 'released' physical presence before 
booting the host OS.

So then Linux boots and, depending on which state the TPM is in, it may 
for example allow you to create keys, perform sign operations or modify 
its Platform Configuration Registers (PCRs) ('extend' the PCRs) even 
though SeaBIOS couldn't initialize the TPM -- in effect one can then use 
the TPM from the VM as one could from an application running on the 
host. This is the ideal case. However, the TPM has a rather complex 
state machine and one can get the TPM into a state called 'disabled' 
where several commands don't work anymore and once it's also 
'deactivated' you can almost not perform any operations with it. In the 
latter case you have to reboot the host to enable and activate the TPM 
again. During the reboot I guess the CPU/chipset is going to pulse a 
line to the TPM and tell it to restart as well, which then in turn 
allows the host's BIOS to run its standard TPM initialization sequence 
again. If one enters the BIOS menu it typically indicates physical 
presence to the TPM using a command, which in turn allows the user to 
then activate and enable the TPM again.

FYI:
The TPM has more than 100 different operations. Some of them are only 
possible if the TPM is in a certain state, for example those to 
enable/activate the TPM require 'physical presence'. Also being able to 
have it create keys for example requires that one has previously taken 
ownership of the TPM.
There are also two possible ways to release ownership. One command can 
be sent to the TPM from the BIOS with 'physical presence' asserted. The 
other can be sent from the OS. In the former case one doesn't need a 
password and the assumption is that the owner of the machine may have 
forgotten the password and therefore needs to have a way to release 
ownership to take ownership again and establish a new password.  In the 
latter case one needs to know the password for the TPM when issuing the 
command -- the encrypted password is part of the command sent to them 
TPM. Also, the side-effect of releasing ownership of the TPM is that all 
previously generated keys won't work anymore.


>
>> and I don't think it makes much
>> sense. I wrote this part of the docs to make people aware of these
>> scenarios so they don't come as a surprise.
>>> Can we return an error, or ignore the release
>>> command? If someone really wants this unsafe behaviour
>>> we could make this an option, off by default.
>>>
>> In effect you'd have to parse every command that goes from the VM to
>> the host and intercept it in the passthrough driver. I don't want to
>> prevent it since it's a valid usage scenario
> That's what I'm asking. Why is this valid and useful?
>
Someone could take ownership of the TPM from within a VM, create keys, 
use them for signing/encryption etc. and then at some point release 
ownership from within that VM again. That I think is a valid scenario, 
but it has these previously mentioned side-effects.
>> but it has side effects
>> (host needs to be rebooted). Even if we were to intercept this
>> command then the user always has the possibility to send commands in
>> an encrypted form (using TPM's transport 'tunnel') where one
>> couldn't intercept this particular command anymore. So, my
>> suggestion is we leave it as it is but we make people aware of these
>> scenarios.
>>
>>     Stefan
> This looks like a major limitation.
>
You'd have to eliminate 'transport mode' completely. The only way to do 
this would be to work with a black- or white-list of TPM ordinals that 
are forbidden/allowed. Well, I am not sure it will make the operation of 
the device easier and users would really know the details. The libtpms 
based TPM will improve that situation in terms of the usage model being 
the same as if one was to use the TPM from the host directly.

    Stefan

Patch

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,471 @@ 
+/*
+ *  passthrough TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Authors:
+ *    Stefan Berger <stefanb@us.ibm.com>
+ *
+ *  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 "qemu-error.h"
+#include "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.h"
+
+/* #define DEBUG_TPM */
+
+/* data structures */
+
+typedef struct TPMPassthruThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+    TPMBackend *tb;
+} TPMPassthruThreadParams;
+
+struct TPMPassthruState {
+    QemuThread thread;
+    bool thread_terminate;
+    bool thread_running;
+
+    TPMPassthruThreadParams tpm_thread_params;
+
+    char tpm_dev[64];
+    int tpm_fd;
+    bool had_startup_error;
+};
+
+#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+
+/* 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)
+{
+    TPMPassthruThreadParams *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) {
+                error_report("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) {
+                error_report("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 int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
+{
+    const char *value;
+    char buf[64];
+    int n;
+
+    value = qemu_opt_get(opts, "path");
+    if (!value) {
+        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+    }
+
+    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)) {
+        error_report("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) {
+        return 1;
+    }
+
+    tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+    if (tb->s.tpm_pt->tpm_fd < 0) {
+        error_report("Cannot access TPM device using '%s'.\n",
+                     tb->s.tpm_pt->tpm_dev);
+        goto err_exit;
+    }
+
+    if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+        error_report("'%s' is not a TPM device.\n",
+                     tb->s.tpm_pt->tpm_dev);
+        goto err_close_tpmdev;
+    }
+
+    return 0;
+
+ err_close_tpmdev:
+    close(tb->s.tpm_pt->tpm_fd);
+    tb->s.tpm_pt->tpm_fd = -1;
+
+ err_exit:
+    g_free(tb->parameters);
+    tb->parameters = NULL;
+
+    return 1;
+}
+
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
+{
+    TPMBackend *tb;
+
+    tb = g_malloc0(sizeof(TPMBackend));
+    if (tb == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        return NULL;
+    }
+
+    tb->s.tpm_pt = g_malloc0(sizeof(TPMPassthruState));
+    if (tb->s.tpm_pt == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        g_free(tb);
+        return NULL;
+    }
+
+    tb->id = g_strdup(id);
+    if (tb->id == NULL) {
+        error_report("tpm_passthrough: Could not allocate memory.\n");
+        goto err_exit;
+    }
+
+    tb->ops = &tpm_passthrough_driver;
+
+    if (tpm_passthrough_handle_device_opts(opts, tb)) {
+        goto err_exit;
+    }
+
+    return tb;
+
+err_exit:
+    g_free(tb->id);
+    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->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} ,id=@var{id} [,@var{options}]
 @findex -tpmdev
 Backend type must be:
+@option{passthrough}.
 
 The specific backend type will determine the applicable options.
 The @code{-tpmdev} options requires a @code{-device} option.
@@ -1788,6 +1789,34 @@  Use ? to print all available TPM backend
 qemu -tpmdev ?
 @end example
 
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+
+(Linux-host only) Enable access to the host's TPM using the passthrough
+driver.
+
+@option{path} specifies the path to the host's TPM device, i.e., on
+a Linux host this would be @code{/dev/tpm0}.
+@option{path} is optional and by default @code{/dev/tpm0} is used.
+
+Note that the passthrough device must not be used by any application on
+the host.
+
+Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
+the VM's firmware (BIOS/UEFI) will not be able to initialize the
+TPM again and may therefore not show a TPM-specific menu that would
+otherwise allow the user to configure the TPM.
+Also, if TPM ownership is released from within a VM then this will
+require a reboot of the host and the user will have to enter the host's
+firmware menu to enable and activate the TPM again. If the TPM is left
+disabled and deactivated most TPM commands will fail.
+
+To create a passthrough TPM use the following two options:
+@example
+-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
+@end example
+Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
+@code{tpmdev=tpm0} 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,35 @@ 
 #include "qerror.h"
 
 static const TPMDriverOps *bes[] = {
+#ifdef CONFIG_TPM_PASSTHROUGH
+    &tpm_passthrough_driver,
+#endif
     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
@@ -7,12 +7,18 @@ 
 struct TPMDriverOps;
 typedef struct TPMDriverOps TPMDriverOps;
 
+typedef struct TPMPassthruState TPMPassthruState;
+
 typedef struct TPMBackend {
     char *id;
     const char *fe_model;
     char *parameters;
     const TPMDriverOps *ops;
 
+    union {
+        TPMPassthruState *tpm_pt;
+    } s;
+
     QLIST_ENTRY(TPMBackend) list;
 } TPMBackend;
 
@@ -79,6 +85,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);
@@ -86,5 +116,9 @@  TPMBackend *qemu_find_tpm(const char *id
 void do_info_tpm(Monitor *mon);
 void tpm_display_backend_drivers(void);
 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 */