Patchwork [V2,8/9] Implementation of the libtpms-based backend

login
register
mail settings
Submitter Stefan Berger
Date March 30, 2011, 7:42 p.m.
Message ID <20110330194238.709720337@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/88958/
State New
Headers show

Comments

Stefan Berger - March 30, 2011, 7:42 p.m.
This patch provides the glue for the TPM TIS interface (frontend) to
the libtpms that provides the actual TPM functionality.

Some details:

This part of the patch provides support for the spawning of a thread
that will interact with the libtpms-based TPM. It expects a signal
from the frontend to wake and pick up the TPM command that is supposed
to be processed and delivers the response packet using a callback
function provided by the frontend.

The backend connectects itself to the frontend by filling out an interface
structure with pointers to the function implementing support for various
operations.

In this part a structure with callback functions with is registered with
libtpms. Those callback functions mostly deal with persistent storage.

The libtpms-based backend implements functionality to write into a 
Qemu block storage device rather than to plain files. With that we
can support VM snapshotting and we also get the possibility to use
encrypted QCoW2 for free. Thanks to Anthony for pointing this out.
The storage part of the driver has been split off into its own patch.

v2:
  - the directory's checksum is now calculated using zlib's crc32
  - fixes to adhere to the qemu coding style


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

---
 configure        |    3 
 hw/tpm_builtin.c |  424 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 hw/tpm_tis.h     |   17 ++
 3 files changed, 423 insertions(+), 21 deletions(-)

Patch

Index: qemu-git/hw/tpm_tis.h
===================================================================
--- qemu-git.orig/hw/tpm_tis.h
+++ qemu-git/hw/tpm_tis.h
@@ -137,4 +137,21 @@  static inline void dumpBuffer(FILE *stre
     fprintf(stream, "\n");
 }
 
+static inline void clear_sized_buffer(TPMSizedBuffer *tpmsb)
+{
+    if (tpmsb->buffer) {
+       tpmsb->size = 0;
+       qemu_free(tpmsb->buffer);
+       tpmsb->buffer = NULL;
+    }
+}
+
+static inline void set_sized_buffer(TPMSizedBuffer *tpmsb,
+                                    uint8_t *buffer, uint32_t size)
+{
+    clear_sized_buffer(tpmsb);
+    tpmsb->size = size;
+    tpmsb->buffer = buffer;
+}
+
 #endif /* _HW_TPM_TIS_H */
Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -1,4 +1,5 @@ 
 /*
+ *  built-in TPM support using libtpms
  *
  *  Copyright (c) 2010, 2011 IBM Corporation
  *  Copyright (c) 2010, 2011 Stefan Berger
@@ -17,15 +18,32 @@ 
  * License along with this library; if not, see <http://www.gnu.org/licenses/>
  */
 
+#include "blockdev.h"
+#include "block_int.h"
 #include "qemu-common.h"
 #include "hw/hw.h"
 #include "hw/tpm_tis.h"
 #include "hw/pc.h"
 
+#include <libtpms/tpm_library.h>
+#include <libtpms/tpm_error.h>
+#include <libtpms/tpm_memory.h>
+#include <libtpms/tpm_nvfilename.h>
+#include <libtpms/tpm_tis.h>
+
+#include <zlib.h>
+
+
+/* following define will be removed once SeaBIOS has TPM support */
+
 //#define DEBUG_TPM
 //#define DEBUG_TPM_SR /* suspend - resume */
 
 
+#define SAVESTATE_TYPE 'S'
+#define PERMSTATE_TYPE 'P'
+#define VOLASTATE_TYPE 'V'
+
 /* data structures */
 
 typedef struct ThreadParams {
@@ -40,11 +58,17 @@  typedef struct ThreadParams {
 static QemuThread thread;
 
 static QemuMutex state_mutex; /* protects *_state below */
+static QemuCond bs_write_result_cond;
+static TPMSizedBuffer permanent_state = { .size = 0, .buffer = NULL, };
+static TPMSizedBuffer volatile_state  = { .size = 0, .buffer = NULL, };
+static TPMSizedBuffer save_state      = { .size = 0, .buffer = NULL, };
+static int pipefd[2] =  {-1, -1};
 
 static bool thread_terminate = false;
 static bool tpm_initialized = false;
 static bool had_fatal_error = false;
 static bool had_startup_error = false;
+static bool need_read_volatile = false;
 
 static ThreadParams tpm_thread_params;
 
@@ -58,12 +82,54 @@  static const unsigned char tpm_std_fatal
 static char dev_description[80];
 
 
+static int tpmlib_get_prop(enum TPMLIB_TPMProperty prop)
+{
+    int result;
+
+    TPM_RESULT res = TPMLIB_GetTPMProperty(prop, &result);
+
+    assert(res == TPM_SUCCESS);
+
+    return result;
+}
+
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+static unsigned int memsum(const unsigned char *buf, int len)
+{
+     int res = 0, i;
+
+     for (i = 0; i < len; i++) {
+          res += buf[i];
+     }
+
+     return res;
+}
+#endif
+
+
 /**
  * Start the TPM. If it had been started before, then terminate and start
  * it again.
  */
 static int startup_tpm(void)
 {
+    TPM_RESULT res;
+
+    if (tpm_initialized) {
+        TPMLIB_Terminate();
+        tpm_initialized = false;
+    }
+
+    res = TPMLIB_MainInit();
+    if (res != TPM_SUCCESS) {
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr,"tpm: Error: Call to TPMLIB_MainInit() failed (rc=%d)\n",
+                       res);
+#endif
+        return 1;
+    }
+
     tpm_initialized = true;
 
 #if defined DEBUG_TPM || defined DEBUG_TPM_SR
@@ -84,6 +150,13 @@  static int late_startup_tpm(void)
 {
     int rc;
 
+    if (startup_bs(bs)) {
+        had_fatal_error = 1;
+        return 1;
+    }
+
+    load_tpm_state_from_bs(bs);
+
     rc  = startup_tpm();
     if (rc) {
         had_fatal_error = 1;
@@ -107,6 +180,7 @@  static void terminate_tpm_thread(void)
         memset(&thread, 0, sizeof(thread));
 
         if (tpm_initialized) {
+            TPMLIB_Terminate();
             tpm_initialized = false;
         }
     }
@@ -116,15 +190,21 @@  static void terminate_tpm_thread(void)
 static void tpm_atexit(void)
 {
     terminate_tpm_thread();
+
+    close(pipefd[0]);
+    pipefd[0] = -1;
+
+    close(pipefd[1]);
+    pipefd[1] = -1;
 }
 
 
 static void *mainLoop(void *d)
 {
-    int res = 0;
     ThreadParams *tParams = (ThreadParams *)d;
     uint32_t in_len, out_len;
     uint8_t *in, *out;
+    TPM_RESULT res;
     uint32_t resp_size; /* total length of response */
 
     /* start command processing */
@@ -176,15 +256,15 @@  static void *mainLoop(void *d)
 
                 resp_size = 0;
 
+                /* TPMLIB_Process may realloc the response buffer */
+                res = TPMLIB_Process(
+                    &tParams->tpm_state->loc[g_locty].r_buffer.buffer,
+                    &resp_size, &out_len,
+                    in, in_len);
 
-                // !!! Send command to TPM & wait for response
-
-
-                if (res != 0) {
+                if (res != TPM_SUCCESS) {
 #ifdef DEBUG_TPM
-                    fprintf(stderr,
-                            "Sending/receiving TPM request/response "
-                            "failed\n");
+                    fprintf(stderr,"TPMLIB_Process() failed\n");
 #endif
                     had_fatal_error = 1;
                 }
@@ -200,7 +280,9 @@  static void *mainLoop(void *d)
             }
 #ifdef DEBUG_TPM
             fprintf(stderr,"sending %d bytes to VM\n", resp_size);
-            dumpBuffer(stdout, out, resp_size);
+            dumpBuffer(stdout,
+                       tParams->tpm_state->loc[g_locty].r_buffer.buffer,
+                       resp_size);
 #endif
             tParams->recv_data_callback(tParams->tpm_state, g_locty);
         } while (in_len > 0);
@@ -210,6 +292,203 @@  static void *mainLoop(void *d)
 }
 
 
+/*****************************************************************
+ * call back functions for the libtpms TPM library
+ ****************************************************************/
+static TPM_RESULT tpm_nvram_init(void)
+{
+    return TPM_SUCCESS;
+}
+
+
+static TPM_RESULT tpm_nvram_loaddata(unsigned char **data,
+                                   uint32_t *length,
+                                   size_t tpm_number __attribute__((unused)),
+                                   const char *name)
+{
+    TPM_RESULT rc = TPM_SUCCESS;
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr,"tpm: TPM_NVRAM_LoadData: tpm_number = %d, name = %s\n",
+                   (int)tpm_number, name);
+#endif
+    *length = 0;
+
+    if (!strcmp(name, TPM_PERMANENT_ALL_NAME)) {
+        *length = permanent_state.size;
+
+        if (*length == 0) {
+            rc = TPM_RETRY;
+        } else {
+            /*
+             * keep the permanent state for
+             * as long as possible. We may
+             * be in a resume operation and only
+             * get the volatile state later on when
+             * Qemu provides the state.
+             * Once the volatile state is there,
+             * we can discard the permanent state,
+             * otherwise the perment state will be
+             * discarded in other places
+             */
+            if (volatile_state.size == 0) {
+                rc = TPM_Malloc(data, *length);
+                if (rc == 0)
+                    memcpy(*data, permanent_state.buffer, *length);
+            } else {
+                *data = permanent_state.buffer;
+
+                permanent_state.size = 0;
+                permanent_state.buffer = NULL;
+            }
+        }
+    } else if (!strcmp(name, TPM_VOLATILESTATE_NAME)) {
+        *length = volatile_state.size;
+        if (*length == 0) {
+            rc = TPM_RETRY;
+        } else {
+            *data = volatile_state.buffer;
+
+            volatile_state.size = 0;
+            volatile_state.buffer = NULL;
+        }
+    } else if (!strcmp(name, TPM_SAVESTATE_NAME)) {
+        *length = save_state.size;
+        if (*length == 0) {
+            rc = TPM_RETRY;
+        } else {
+            *data = save_state.buffer;
+            save_state.size = 0;
+            save_state.buffer = NULL;
+        }
+    }
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: Read %d bytes of state [sum=%08x]; rc = %d\n",
+            *length, memsum(*data, *length), rc);
+#endif
+
+    return rc;
+}
+
+
+/*
+ * Called by the TPM when permanent data, savestate or volatile state
+ * is updated or needs to be saved.
+ * Primarily we care about savestate and permanent data here.
+ */
+static TPM_RESULT tpm_nvram_storedata(const unsigned char *data,
+                                   uint32_t length,
+                                   size_t tpm_number __attribute__((unused)),
+                                   const char *name)
+{
+    TPM_RESULT rc = TPM_SUCCESS;
+    char what;
+    TPMSizedBuffer *tsb = NULL;
+
+    if (!strcmp(name, TPM_PERMANENT_ALL_NAME)) {
+        tsb = &permanent_state;
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr,"tpm: STORING %5d BYTES OF PERMANENT ALL  [sum=%08x]\n",
+                length, memsum(data, length));
+#endif
+        what = PERMSTATE_TYPE;
+    } else if (!strcmp(name, TPM_SAVESTATE_NAME)) {
+        tsb = &save_state;
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr,"tpm: STORING %5d BYTES OF SAVESTATE      [sum=%08x]\n",
+                length, memsum(data, length));
+#endif
+        what = SAVESTATE_TYPE;
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    } else if (!strcmp(name, TPM_VOLATILESTATE_NAME)) {
+        fprintf(stderr,"tpm: GOT     %5d BYTES OF VOLATILE STATE [sum=%08x]\n",
+                length, memsum(data, length));
+#endif
+    }
+
+    if (tsb) {
+        /* we may get called here during TPMLIB_MainInit() rather than
+           while running as a thread. Skip writing the state until we
+           process the first command. */
+        qemu_mutex_lock(&state_mutex);
+
+        set_sized_buffer(tsb, (unsigned char *)data, length);
+
+        if (request_sync_to_bs(what)) {
+            rc = TPM_FAIL;
+        }
+
+        /* TPM library will free */
+        tsb->size = 0;
+        tsb->buffer = NULL;
+
+        qemu_mutex_unlock(&state_mutex);
+    }
+
+    if (had_fatal_error) {
+        rc = TPM_FAIL;
+    }
+
+    return rc;
+}
+
+
+static TPM_RESULT tpm_nvram_deletename(
+                                  size_t tpm_number __attribute__((unused)),
+                                  const char *name,
+                                  TPM_BOOL mustExist)
+{
+    TPM_RESULT rc = TPM_SUCCESS;
+
+    /* only handle the savestate here */
+    if (!strcmp(name, TPM_SAVESTATE_NAME)) {
+        qemu_mutex_lock(&state_mutex);
+
+        clear_sized_buffer(&save_state);
+
+        if (request_sync_to_bs(SAVESTATE_TYPE)) {
+            rc = TPM_FAIL;
+        }
+
+        qemu_mutex_unlock(&state_mutex);
+    } else if (!strcmp(name, TPM_VOLATILESTATE_NAME)) {
+        qemu_mutex_lock(&state_mutex);
+
+        clear_sized_buffer(&volatile_state);
+
+        if (request_sync_to_bs(VOLASTATE_TYPE)) {
+            rc = TPM_FAIL;
+        }
+
+        qemu_mutex_unlock(&state_mutex);
+    }
+
+    return rc;
+}
+
+
+static TPM_RESULT tpm_io_init(void)
+{
+    return TPM_SUCCESS;
+}
+
+
+static TPM_RESULT tpm_io_getlocality(TPM_MODIFIER_INDICATOR *localityModifier)
+{
+    *localityModifier = (TPM_MODIFIER_INDICATOR)g_locty;
+
+    return TPM_SUCCESS;
+}
+
+
+static TPM_RESULT tpm_io_getphysicalpresence(TPM_BOOL *physicalPresence)
+{
+    *physicalPresence = FALSE;
+
+    return TPM_SUCCESS;
+}
+
 
 /*****************************************************************/
 
@@ -229,8 +508,12 @@  static void reset(void)
         terminate_tpm_thread();
     }
 
+    clear_sized_buffer(&permanent_state);
+    clear_sized_buffer(&save_state);
+    clear_sized_buffer(&volatile_state);
     had_fatal_error = false;
     thread_terminate = false;
+    need_read_volatile = false;
     had_startup_error = false;
 
     qemu_thread_create(&thread, mainLoop, &tpm_thread_params);
@@ -261,32 +544,92 @@  static int instantiate_with_volatile_dat
 #ifdef DEBUG_TPM_SR
         fprintf(stderr,"tpm: This is resume of a SNAPSHOT?!\n");
 #endif
-        // !!! Xen does not support this ...
         tis_reset_for_snapshot_resume(s);
     }
 
+    /* we need to defer the read since we will not have the encryption key
+       in case storage is encrypted at this point */
+    need_read_volatile = true;
+
     return 0;
 }
 
 
+struct libtpms_callbacks callbacks = {
+    .sizeOfStruct               = sizeof(struct libtpms_callbacks),
+    .tpm_nvram_init             = tpm_nvram_init,
+    .tpm_nvram_loaddata         = tpm_nvram_loaddata,
+    .tpm_nvram_storedata        = tpm_nvram_storedata,
+    .tpm_nvram_deletename       = tpm_nvram_deletename,
+    .tpm_io_init                = tpm_io_init,
+    .tpm_io_getlocality         = tpm_io_getlocality,
+    .tpm_io_getphysicalpresence = tpm_io_getphysicalpresence,
+};
+
+
 static int init(TPMState *s, TPMRecvDataCB *recv_data_cb)
 {
+    int flags;
+
+    bs = bdrv_find("vtpm-nvram");
+    if (bs == NULL) {
+        fprintf(stderr, "The vtpm-nvram driver was not found.\n");
+        goto err_exit;
+    }
+
+    if (TPMLIB_RegisterCallbacks(&callbacks) != TPM_SUCCESS) {
+        goto err_exit;
+    }
+
+    if (check_bs(bs)) {
+        goto err_exit;
+    }
+
     tpm_thread_params.tpm_state = s;
     tpm_thread_params.recv_data_callback = recv_data_cb;
 
     qemu_mutex_init(&state_mutex);
+    qemu_cond_init(&bs_write_result_cond);
 
-    // !!! Do necessary initialization here
+    if (pipe(pipefd)) {
+        goto err_exit;
+    }
+
+    flags = fcntl(pipefd[0], F_GETFL);
+    if (flags < 0) {
+        goto err_exit_close_pipe;
+    }
+
+    if (fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK ) < 0) {
+        goto err_exit_close_pipe;
+    }
+
+    qemu_set_fd_handler(pipefd[0], fulfill_sync_to_bs_request, NULL, NULL);
 
     atexit(tpm_atexit);
 
     return 0;
+
+err_exit_close_pipe:
+    close(pipefd[0]);
+    pipefd[0] = -1;
+    close(pipefd[1]);
+    pipefd[1] = -1;
+
+err_exit:
+    return 1;
 }
 
 
 static bool get_tpm_established_flag(void)
 {
-    return false;
+    TPM_BOOL tpmEstablished = false;
+
+    if (tpm_initialized) {
+        TPM_IO_TpmEstablished_Get(&tpmEstablished);
+    }
+
+    return (bool)tpmEstablished;
 }
 
 
@@ -298,10 +641,16 @@  static bool get_startup_error(void)
 
 /**
  * This function is called by tpm_tis.c once the TPM has processed
- * the last command and returned the response to the TIS.
+ * the last command and returned the response to the TIS. Since we
+ * store the volatile state into the block storage device we leave
+ * the provided buffer untouched.
  */
 static int save_volatile_data(void)
 {
+    TPM_RESULT res;
+    unsigned char *buffer;
+    uint32_t buflen;
+
     if (!tpm_initialized) {
         /* TPM was never initialized
            volatile_state.buffer may be NULL if TPM was never used.
@@ -309,20 +658,50 @@  static int save_volatile_data(void)
         return 0;
     }
 
+    /* have the serialized state written to a buffer only */
+#ifdef DEBUG_TPM_SR
+    fprintf(stderr,"tpm: Calling TPMLIB_VolatileAll_Store()\n");
+#endif
+    res = TPMLIB_VolatileAll_Store(&buffer, &buflen);
+
+    if (res != TPM_SUCCESS) {
+#ifdef DEBUG_TPM_SR
+        fprintf(stderr,"tpm: Error: Could not store TPM volatile state\n");
+#endif
+        return 1;
+    }
+
+#ifdef DEBUG_TPM_SR
+    fprintf(stderr,"tpm: got %d bytes of volatilestate [sum=%08x]\n",
+            buflen, memsum(buffer, buflen));
+#endif
+
+    set_sized_buffer(&volatile_state, buffer, buflen);
+    if (write_state_to_bs(VOLASTATE_TYPE)) {
+        return 1;
+    }
+    volatile_state.size = 0;
+    volatile_state.buffer = NULL;
+
+    /* make sure that everything has been written to disk */
+    fulfill_sync_to_bs_request(NULL);
+
     return 0;
 }
 
 
 static size_t realloc_buffer(TPMSizedBuffer *sb)
 {
-    size_t wanted_size = 4096;
+    TPM_RESULT res;
+    size_t wanted_size = tpmlib_get_prop(TPMPROP_TPM_BUFFER_MAX);
 
     if (sb->size != wanted_size) {
-        sb->buffer = qemu_realloc(&sb->buffer, wanted_size);
-        if (sb->buffer != NULL)
+        res = TPM_Realloc(&sb->buffer, wanted_size);
+        if (res == TPM_SUCCESS) {
             sb->size = wanted_size;
-        else
+        } else {
             sb->size = 0;
+        }
     }
     return sb->size;
 }
@@ -334,7 +713,8 @@  static const char *create_desc(void)
 
     if (!done) {
         snprintf(dev_description, sizeof(dev_description),
-                 "Skeleton TPM backend");
+                 "Qemu's built-in TPM; requires %ukb of block storage",
+                 MINIMUM_BS_SIZE_KB);
         done = 1;
     }
 
@@ -342,13 +722,15 @@  static const char *create_desc(void)
 }
 
 
+#define TPM_OPTS "id=vtpm-nvram"
+
 static bool handle_options(QemuOpts *opts)
 {
     const char *value;
 
     value = qemu_opt_get(opts, "path");
     if (value) {
-        // !!! handle path parameter
+        drive_add(IF_NONE, -1, value, TPM_OPTS);
     } else {
         fprintf(stderr,"-tpm is missing path= parameter\n");
         return false;
@@ -357,8 +739,8 @@  static bool handle_options(QemuOpts *opt
 }
 
 
-BackendTPMDriver skeleton = {
-    .id                             = "skeleton",
+BackendTPMDriver builtin = {
+    .id                             = "builtin",
     .desc                           = create_desc,
     .handle_options                 = handle_options,
     .init                           = init,