From: Stefan Berger <stefanb@linux.vnet.ibm.com>
To: qemu-devel@nongnu.org
Subject: Re: [Qemu-devel] [PATCH] Add a TPM Passthrough backend driver implementation
Date: Mon, 15 Aug 2011 13:34:51 -0400 [thread overview]
Message-ID: <4E4958BB.1000603@linux.vnet.ibm.com> (raw)
In-Reply-To: <1313163072-15152-1-git-send-email-andreas.niederl@iaik.tugraz.at>
On 08/12/2011 11:31 AM, Andreas Niederl wrote:
> This patch is based of off version 7 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
>
The code looks good to me. Though hopefully nothing on the host is using
the TPM in this case so that all PCRs are cleared once the VM starts --
this may otherwise create problems with attestation.
Also, this device may need to be marked as unmigratable -- I am not sure
how to do this though. There is an API in savevm.c to register a device
as unmigratable, but I don't think we can call it
(register_device_unmigratable()). It may either require some rearranging
of code in tpm_tis where the VMStateDescription structure also has a
field to mark a device as unmigratable (used by usb emulators for
example) or the introduction of a new API call that allows a device to
be marked as unmigratable after it has been registered.
Regards,
Stefan
> Regards,
> Andreas Niederl
>
> [1] http://trustedjava.sourceforge.net/
>
> Signed-off-by: Andreas Niederl<andreas.niederl@iaik.tugraz.at>
> ---
> Makefile.target | 2 +-
> hw/tpm_passthrough.c | 450 ++++++++++++++++++++++++++++++++++++++++++++++++++
> tpm.c | 1 +
> tpm.h | 1 +
> 4 files changed, 453 insertions(+), 1 deletions(-)
> create mode 100644 hw/tpm_passthrough.c
>
> diff --git a/Makefile.target b/Makefile.target
> index f4d42d4..7f19f28 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -233,7 +233,7 @@ obj-i386-y += debugcon.o multiboot.o
> obj-i386-y += pc_piix.o
> obj-i386-$(CONFIG_KVM) += kvmclock.o
> obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o
> -obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o
> +obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o tpm_passthrough.o
> obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
>
> ifdef CONFIG_TPM_BUILTIN
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..aabfea2
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,450 @@
> +/*
> + * passthrough TPM driver
> + *
> + * Copyright (c) 2010, 2011 IBM Corporation
> + * Copyright (c) 2010, 2011 Stefan Berger
> + *
> + * 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
> +//#define DEBUG_TPM_SR /* suspend - resume */
> +
> +
> +/* data structures */
> +
> +typedef struct ThreadParams {
> + TPMState *tpm_state;
> +
> + TPMRecvDataCB *recv_data_callback;
> +
> + int fd;
> +} ThreadParams;
> +
> +
> +/* local variables */
> +
> +static QemuThread thread;
> +
> +static bool thread_terminate = false;
> +static bool thread_running = false;
> +
> +static ThreadParams tpm_thread_params;
> +
> +static const unsigned char tpm_std_fatal_error_response[10] = {
> + 0x00, 0xc4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09 /* TPM_FAIL */
> +};
> +
> +static char dev_description[80];
> +
> +static char tpm_dev[255];
> +static int tpmfd = -1;
> +
> +static bool had_startup_error = false;
> +
> +
> +/* borrowed from qemu-char.c */
> +static int 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 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_write_std_fatal_error_response(uint8_t *out,
> + uint8_t *in, uint32_t in_len)
> +{
> + memcpy(out, tpm_std_fatal_error_response,
> + sizeof(tpm_std_fatal_error_response));
> + out[1] = (in_len> 2&& in[1]>= 0xc1&& in[1]<= 0xc3)
> + ? in[1] + 3
> + : 0xc4;
> +}
> +
> +static void *tpm_passthrough_main_loop(void *d)
> +{
> + ThreadParams *thr_parms = d;
> + uint32_t in_len, out_len;
> + uint8_t *in, *out;
> + uint8_t locty;
> + int ret;
> +
> +#ifdef DEBUG_TPM
> + fprintf(stderr, "tpm: THREAD IS STARTING\n");
> +#endif
> +
> + /* start command processing */
> + while (!thread_terminate) {
> + /* receive and handle commands */
> + in_len = 0;
> + do {
> +#ifdef DEBUG_TPM
> + fprintf(stderr, "tpm: waiting for commands...\n");
> +#endif
> +
> + if (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 (thread_terminate) {
> + break;
> + }
> +
> + locty = thr_parms->tpm_state->command_locty;
> +
> + in = thr_parms->tpm_state->loc[locty].w_buffer.buffer;
> + in_len = thr_parms->tpm_state->loc[locty].w_offset;
> + out = thr_parms->tpm_state->loc[locty].r_buffer.buffer;
> + out_len = thr_parms->tpm_state->loc[locty].r_buffer.size;
> +
> + ret = unix_write(tpmfd, in, in_len);
> + if (ret< 0) {
> + fprintf(stderr, "tpm: error while transmitting data to host tpm"
> + ": %s (%i)\n",
> + strerror(errno), errno);
> + tpm_write_std_fatal_error_response(out, in, in_len);
> + continue;
> + }
> +
> + ret = unix_read(tpmfd, out, out_len);
> + if (ret< 0) {
> + fprintf(stderr, "tpm: error while reading data from host tpm"
> + ": %s (%i)\n",
> + strerror(errno), errno);
> + tpm_write_std_fatal_error_response(out, in, in_len);
> + continue;
> + }
> +
> + thr_parms->recv_data_callback(thr_parms->tpm_state, locty);
> + } while (in_len> 0);
> + }
> +
> +#ifdef DEBUG_TPM
> + fprintf(stderr, "tpm: THREAD IS ENDING\n");
> +#endif
> +
> + thread_running = false;
> +
> + return NULL;
> +}
> +
> +
> +static void tpm_passthrough_terminate_tpm_thread(void)
> +{
> + if (!thread_running) {
> + return;
> + }
> +
> +#if defined DEBUG_TPM || defined DEBUG_TPM_SR
> + fprintf(stderr, "tpm: TERMINATING RUNNING TPM THREAD\n");
> +#endif
> +
> + if (!thread_terminate) {
> + thread_terminate = true;
> +
> + qemu_mutex_lock (&tpm_thread_params.tpm_state->state_lock);
> + qemu_cond_signal (&tpm_thread_params.tpm_state->to_tpm_cond);
> + qemu_mutex_unlock(&tpm_thread_params.tpm_state->state_lock);
> +
> + while (thread_running) {
> + usleep(100000);
> + }
> + memset(&thread, 0, sizeof(thread));
> + }
> +}
> +
> +
> +static void tpm_passthrough_tpm_atexit(void)
> +{
> + tpm_passthrough_terminate_tpm_thread();
> +
> + close(tpmfd);
> + tpmfd = -1;
> +}
> +
> +
> +/**
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_startup_tpm(void)
> +{
> + /* terminate a running TPM */
> + tpm_passthrough_terminate_tpm_thread();
> +
> + /* reset the flag so the thread keeps on running */
> + thread_terminate = false;
> +
> + qemu_thread_create(&thread, tpm_passthrough_main_loop,&tpm_thread_params);
> +
> + thread_running = true;
> +
> + return 0;
> +}
> +
> +
> +static int tpm_passthrough_do_startup_tpm(void)
> +{
> + int rc;
> +
> + rc = tpm_passthrough_startup_tpm();
> + if (rc) {
> + had_startup_error = true;
> + }
> + return rc;
> +}
> +
> +
> +static int tpm_passthrough_early_startup_tpm(void)
> +{
> + return tpm_passthrough_do_startup_tpm();
> +}
> +
> +
> +static int tpm_passthrough_late_startup_tpm(void)
> +{
> + return tpm_passthrough_do_startup_tpm();
> +}
> +
> +
> +static void tpm_passthrough_reset(void)
> +{
> +#if defined DEBUG_TPM || defined DEBUG_TPM_SR
> + fprintf(stderr, "tpm: CALL TO TPM_RESET!\n");
> +#endif
> +
> + tpm_passthrough_terminate_tpm_thread();
> +
> + had_startup_error = false;
> +}
> +
> +
> +/*
> + * Since the null driver does not have much persistent storage
> + * there is not much to do here...
> + */
> +static int tpm_passthrough_instantiate_with_volatile_data(TPMState *s)
> +{
> + if (thread_running) {
> +#ifdef DEBUG_TPM_SR
> + fprintf(stderr, "tpm: This is resume of a SNAPSHOT\n");
> +#endif
> + tis_reset_for_snapshot_resume(s);
> + }
> +
> + return 0;
> +}
> +
> +
> +static int tpm_passthrough_init(TPMState *s, TPMRecvDataCB *recv_data_cb)
> +{
> + tpm_thread_params.tpm_state = s;
> + tpm_thread_params.recv_data_callback = recv_data_cb;
> +
> + tpmfd = open(tpm_dev, O_RDWR);
> + if (tpmfd< 0) {
> + fprintf(stderr,
> + "Cannot open device '%s' from tpm's path option.\n",
> + tpm_dev);
> + goto err_exit;
> + }
> +
> + atexit(tpm_passthrough_tpm_atexit);
> +
> + return 0;
> +
> +err_exit:
> + if (tpmfd>= 0) {
> + close(tpmfd);
> + tpmfd = -1;
> + }
> + return 1;
> +}
> +
> +
> +static bool tpm_passthrough_get_tpm_established_flag(void)
> +{
> + return false;
> +}
> +
> +
> +static bool tpm_passthrough_get_startup_error(void)
> +{
> + return had_startup_error;
> +}
> +
> +
> +/**
> + * This function is called by tpm_tis.c once the TPM has processed
> + * the last command and returned the response to the TIS.
> + */
> +static int tpm_passthrough_save_volatile_data(void)
> +{
> + return 0;
> +}
> +
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> + size_t wanted_size = 4096;
> +
> + if (sb->size != wanted_size) {
> + sb->buffer = qemu_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)
> +{
> + static int done;
> +
> + if (!done) {
> + snprintf(dev_description, sizeof(dev_description),
> + "Passthrough TPM backend driver");
> + done = 1;
> + }
> +
> + return dev_description;
> +}
> +
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id,
> + const char *model)
> +{
> + TPMBackend *driver;
> + const char *value;
> +
> + driver = qemu_malloc(sizeof(TPMBackend));
> + if (!driver) {
> + fprintf(stderr, "Could not allocate memory.\n");
> + return NULL;
> + }
> + driver->id = qemu_strdup(id);
> + driver->model = NULL;
> + if (model)
> + driver->model = qemu_strdup(model);
> + driver->ops =&tpm_passthrough_driver;
> +
> + value = qemu_opt_get(opts, "path");
> + if (value) {
> + if (access(value, R_OK|W_OK)) {
> + fprintf(stderr,
> + "Cannot access device '%s' from tpm's path option.\n",
> + value);
> + goto err_exit;
> + }
> + strncpy(tpm_dev, value, sizeof(tpm_dev));
> + } else {
> + fprintf(stderr, "-tpm is missing path= parameter\n");
> + goto err_exit;
> + }
> +
> + return driver;
> +
> +err_exit:
> + qemu_free(driver->id);
> + if (driver->model)
> + qemu_free(driver->model);
> + qemu_free(driver);
> + return NULL;
> +}
> +
> +
> +static void tpm_passthrough_destroy(TPMBackend *driver)
> +{
> + qemu_free(driver->id);
> + if (driver->model)
> + qemu_free(driver->model);
> + qemu_free(driver);
> +}
> +
> +
> +TPMDriverOps tpm_passthrough_driver = {
> + .id = "passthrough",
> + .desc = tpm_passthrough_create_desc,
> + .job_for_main_thread = NULL,
> + .create = tpm_passthrough_create,
> + .destroy = tpm_passthrough_destroy,
> + .init = tpm_passthrough_init,
> + .early_startup_tpm = tpm_passthrough_early_startup_tpm,
> + .late_startup_tpm = tpm_passthrough_late_startup_tpm,
> + .realloc_buffer = tpm_passthrough_realloc_buffer,
> + .reset = tpm_passthrough_reset,
> + .had_startup_error = tpm_passthrough_get_startup_error,
> + .save_volatile_data = tpm_passthrough_save_volatile_data,
> + .load_volatile_data = tpm_passthrough_instantiate_with_volatile_data,
> + .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/tpm.c b/tpm.c
> index b75fe5c..edcbfe7 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -28,6 +28,7 @@ static const TPMDriverOps *bes[] = {
> #ifdef CONFIG_TPM_BUILTIN
> &tpm_builtin,
> #endif
> +&tpm_passthrough_driver,
> NULL,
> };
>
> diff --git a/tpm.h b/tpm.h
> index d00e599..01cae9a 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -143,5 +143,6 @@ void tpm_measure_buffer(const void *buffer, long length,
>
> extern TPMDriverOps tpm_null_driver;
> extern TPMDriverOps tpm_builtin;
> +extern TPMDriverOps tpm_passthrough_driver;
>
> #endif /* _HW_TPM_CONFIG_H */
prev parent reply other threads:[~2011-08-15 17:35 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-08-12 15:31 [Qemu-devel] [PATCH] Add a TPM Passthrough backend driver implementation Andreas Niederl
2011-08-15 17:34 ` Stefan Berger [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4E4958BB.1000603@linux.vnet.ibm.com \
--to=stefanb@linux.vnet.ibm.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).