Patchwork [V7,06/13] Add a TPM backend skeleton implementation

login
register
mail settings
Submitter Stefan Berger
Date Aug. 10, 2011, 7:29 p.m.
Message ID <20110810193013.610634552@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/109432/
State New
Headers show

Comments

Stefan Berger - Aug. 10, 2011, 7:29 p.m.
This patch provides a TPM backend skeleton implementation. It doesn't do
anything useful (except for returning error response for every TPM command)
but it compiles. It serves as the basis for the libtpms based backend
as well as the null driver backend.

v6:
  - moved unused variable out_len to subsequent patch

v5:
  - the backend interface now has a create and destroy function.
    The former is used during the initialization phase of the TPM
    and the latter to clean up when Qemu terminates.
  - reworked parts of the error path handling where the TPM is
    now used to process commands under error conditions and the callbacks
    make the TPM aware of the error conditions. Only as the last resort
    fault messages are sent by the backend driver circumventing the TPM.

v3:
  - in tpm_builtin.c all functions prefixed with tpm_builtin_
  - build the builtin TPM driver available at this point; it returns
    a failure response message for every command
  - do not try to join the TPM thread but poll for its termination;
    the libtpms-based driver will require Qemu's main thread to write
    data to the block storage device while trying to join

V2:
  - only terminating thread in tpm_atexit if it's running

Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

---
 Makefile.target  |    5 
 configure        |    1 
 hw/tpm_builtin.c |  451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tpm.c            |    3 
 tpm.h            |    1 
 5 files changed, 461 insertions(+)

Patch

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_builtin.c
@@ -0,0 +1,451 @@ 
+/*
+ *  builtin 'null' TPM driver
+ *
+ *  Copyright (c) 2010, 2011 IBM Corporation
+ *  Copyright (c) 2010, 2011 Stefan Berger
+ *
+ * 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;
+} ThreadParams;
+
+
+/* local variables */
+
+static QemuThread thread;
+
+static QemuMutex state_mutex; /* protects *_state below */
+static QemuMutex tpm_initialized_mutex; /* protect tpm_initialized */
+
+static bool thread_terminate;
+static bool tpm_initialized;
+static bool had_fatal_error;
+static bool had_startup_error;
+static bool thread_running;
+
+static ThreadParams tpm_thread_params;
+
+/* locality of the command being executed by libtpms */
+static uint8_t g_locty;
+
+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 void *tpm_builtin_main_loop(void *d)
+{
+    int res = 1;
+    ThreadParams *thr_parms = d;
+    uint32_t in_len;
+    uint8_t *in, *out;
+    uint32_t resp_size; /* total length of response */
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS STARTING\n");
+#endif
+
+    if (res != 0) {
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr, "tpm: Error: TPM initialization failed (rc=%d)\n",
+                res);
+#endif
+    } else {
+        qemu_mutex_lock(&tpm_initialized_mutex);
+
+        tpm_initialized = true;
+
+        qemu_mutex_unlock(&tpm_initialized_mutex);
+    }
+
+    /* 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;
+            }
+
+            g_locty = thr_parms->tpm_state->command_locty;
+
+            in = thr_parms->tpm_state->loc[g_locty].w_buffer.buffer;
+            in_len = thr_parms->tpm_state->loc[g_locty].w_offset;
+
+            if (tpm_initialized) {
+
+#ifdef DEBUG_TPM
+                fprintf(stderr,
+                        "tpm: received %d bytes from VM in locality %d\n",
+                        in_len,
+                        g_locty);
+                dumpBuffer(stderr, in, in_len);
+#endif
+
+                resp_size = 0;
+
+                /* !!! Send command to TPM & wait for response */
+
+                if (res != 0) {
+#ifdef DEBUG_TPM
+                    fprintf(stderr,
+                            "tpm: Sending/receiving TPM request/response "
+                            "failed.\n");
+#endif
+                    had_fatal_error = true;
+                }
+            } else {
+                out = thr_parms->tpm_state->loc[g_locty].r_buffer.buffer;
+
+                resp_size = sizeof(tpm_std_fatal_error_response);
+                memcpy(out, tpm_std_fatal_error_response, resp_size);
+                out[1] = (in_len > 2 && in[1] >= 0xc1 && in[1] <= 0xc3)
+                       ? in[1] + 3
+                       : 0xc4;
+            }
+#ifdef DEBUG_TPM
+            fprintf(stderr, "tpm: sending %d bytes to VM\n", resp_size);
+            dumpBuffer(stderr,
+                       thr_parms->tpm_state->loc[g_locty].r_buffer.buffer,
+                       resp_size);
+#endif
+            thr_parms->recv_data_callback(thr_parms->tpm_state, g_locty);
+        } while (in_len > 0);
+    }
+
+    qemu_mutex_lock(&tpm_initialized_mutex);
+
+    if (tpm_initialized) {
+        tpm_initialized = false;
+    }
+
+    qemu_mutex_unlock(&tpm_initialized_mutex);
+
+#ifdef DEBUG_TPM
+    fprintf(stderr, "tpm: THREAD IS ENDING\n");
+#endif
+
+    thread_running = false;
+
+    return NULL;
+}
+
+
+static void tpm_builtin_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);
+
+        /* The thread will set thread_running = false; it may
+         * still ask us to write data to the disk, though.
+         */
+        while (thread_running) {
+            /* !!! write data to disk if necessary */
+            usleep(100000);
+        }
+
+        memset(&thread, 0, sizeof(thread));
+    }
+}
+
+
+static void tpm_builtin_tpm_atexit(void)
+{
+    tpm_builtin_terminate_tpm_thread();
+}
+
+
+/**
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_builtin_startup_tpm(void)
+{
+    /* terminate a running TPM */
+    tpm_builtin_terminate_tpm_thread();
+
+    /* reset the flag so the thread keeps on running */
+    thread_terminate = false;
+
+    qemu_thread_create(&thread, tpm_builtin_main_loop, &tpm_thread_params);
+
+    thread_running = true;
+
+    return 0;
+}
+
+
+static int tpm_builtin_do_startup_tpm(void)
+{
+    return tpm_builtin_startup_tpm();
+}
+
+
+/*
+ * Startup the TPM early. This only works for non-encrypted
+ * BlockStorage, since otherwise we would not have the key yet.
+ */
+static int tpm_builtin_early_startup_tpm(void)
+{
+    return tpm_builtin_do_startup_tpm();
+}
+
+
+/*
+ * Start up the TPM before it sees the first command.
+ * We need to do this late since only now we will have the
+ * block storage encryption key and can read the previous
+ * TPM state. During 'reset' the key would not be available.
+ */
+static int tpm_builtin_late_startup_tpm(void)
+{
+    /* give it a new try */
+    had_fatal_error = false;
+    had_startup_error = false;
+
+    if (tpm_builtin_do_startup_tpm()) {
+        had_startup_error = true;
+    }
+
+    return had_startup_error;
+}
+
+
+static void tpm_builtin_reset(void)
+{
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: CALL TO TPM_RESET!\n");
+#endif
+
+    tpm_builtin_terminate_tpm_thread();
+
+    had_fatal_error = false;
+    had_startup_error = false;
+}
+
+
+/*
+ * restore TPM volatile state from given data
+ *
+ * The data are ignore by this driver, instead we read the volatile state
+ * from the TPM block store.
+ *
+ * This function gets called by Qemu when
+ * (1) resuming after a suspend
+ * (2) resuming a snapshot
+ *
+ * (1) works fine since we get call to the reset function as well
+ * (2) requires us to call the reset function ourselves; we do this
+ *     indirectly by calling the tis_reset_for_snapshot_resume();
+ *     a sure indicator of whether this function is called due to a resume
+ *     of a snapshot is that the tread_running variable is 'true'.
+ *
+ */
+static int tpm_builtin_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_builtin_init(TPMState *s, TPMRecvDataCB *recv_data_cb)
+{
+    tpm_thread_params.tpm_state = s;
+    tpm_thread_params.recv_data_callback = recv_data_cb;
+
+    qemu_mutex_init(&state_mutex);
+    qemu_mutex_init(&tpm_initialized_mutex);
+
+    atexit(tpm_builtin_tpm_atexit);
+
+    return 0;
+}
+
+
+static bool tpm_builtin_get_tpm_established_flag(void)
+{
+    return false;
+}
+
+
+static bool tpm_builtin_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_builtin_save_volatile_data(void)
+{
+    if (!tpm_initialized) {
+        /* TPM was never initialized
+           volatile_state.buffer may be NULL if TPM was never used.
+         */
+        return 0;
+    }
+
+    return 0;
+}
+
+
+static size_t tpm_builtin_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_builtin_create_desc(void)
+{
+    static int done;
+
+    if (!done) {
+        snprintf(dev_description, sizeof(dev_description),
+                 "Skeleton TPM backend");
+        done = 1;
+    }
+
+    return dev_description;
+}
+
+
+static TPMBackend *tpm_builtin_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);
+    if (model) {
+        driver->model = qemu_strdup(model);
+    }
+    driver->ops = &tpm_builtin;
+
+    value = qemu_opt_get(opts, "path");
+    if (value) {
+        /* !!! handle file path */
+    } else {
+        fprintf(stderr, "-tpm is missing path= parameter\n");
+        goto err_exit;
+    }
+
+    return driver;
+
+err_exit:
+    qemu_free(driver->id);
+    qemu_free(driver->model);
+    qemu_free(driver);
+    return NULL;
+}
+
+
+static void tpm_builtin_destroy(TPMBackend *driver)
+{
+    qemu_free(driver->id);
+    qemu_free(driver->model);
+    qemu_free(driver);
+}
+
+
+TPMDriverOps tpm_builtin = {
+    .id                       = "builtin",
+    .desc                     = tpm_builtin_create_desc,
+    .job_for_main_thread      = NULL,
+    .create                   = tpm_builtin_create,
+    .destroy                  = tpm_builtin_destroy,
+    .init                     = tpm_builtin_init,
+    .early_startup_tpm        = tpm_builtin_early_startup_tpm,
+    .late_startup_tpm         = tpm_builtin_late_startup_tpm,
+    .realloc_buffer           = tpm_builtin_realloc_buffer,
+    .reset                    = tpm_builtin_reset,
+    .had_startup_error        = tpm_builtin_get_startup_error,
+    .save_volatile_data       = tpm_builtin_save_volatile_data,
+    .load_volatile_data       = tpm_builtin_instantiate_with_volatile_data,
+    .get_tpm_established_flag = tpm_builtin_get_tpm_established_flag,
+};
Index: qemu-git/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -234,6 +234,11 @@  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
+obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
+
+ifdef CONFIG_TPM_BUILTIN
+LIBS+=-ltpms
+endif
 
 # shared objects
 obj-ppc-y = ppc.o
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -3571,6 +3571,7 @@  if test "$tpm" = "yes"; then
   fi
 
   if test "$has_tpm" = "1"; then
+      echo "CONFIG_TPM_BUILTIN=y" >> $config_target_mak
       echo "CONFIG_TPM=y" >> $config_host_mak
   fi
 fi
Index: qemu-git/tpm.c
===================================================================
--- qemu-git.orig/tpm.c
+++ qemu-git/tpm.c
@@ -21,6 +21,9 @@ 
 #if defined(TARGET_I386) || defined(TARGET_X86_64)
 
 static const TPMDriverOps *bes[] = {
+#ifdef CONFIG_TPM_BUILTIN
+    &tpm_builtin,
+#endif
     NULL,
 };
 
Index: qemu-git/tpm.h
===================================================================
--- qemu-git.orig/tpm.h
+++ qemu-git/tpm.h
@@ -108,5 +108,6 @@  void do_info_tpm(Monitor *mon);
 void tpm_display_backend_drivers(FILE *out);
 const TPMDriverOps *tpm_get_backend_driver(const char *id);
 
+extern TPMDriverOps tpm_builtin;
 
 #endif /* _HW_TPM_CONFIG_H */