qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 5/5]: Implementation of the libtpms-based backend


From: Stefan Berger
Subject: [Qemu-devel] [PATCH 5/5]: Implementation of the libtpms-based backend
Date: Thu, 24 Feb 2011 15:06:32 -0500

This patch provides the glue for the TPM TIS interface (frontend) to
the libtpms that provides the actual TPM functionality.

Some details:

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 TPM creates state of varying size, depending for example how many
keys are loaded into it a a certain time. I have calculated the
worst-cases sizes of the different blobs the TPM writes and with that
determined the size of the Qcow2 image. It needs to be 63kb.
'qemu-... -tpm ?' shows this number when this backend driver is
available.


The layout of the TPM's persistent data in the block storage is as follows:

The first sector (512 bytes) holds a primitive directory for the different
types of blobs that the TPM can write. This directory holds a revision
number, a checksum over its content, the number of entries, and the entries
themselves. The entries are described through their absolute offsets, their
maximum sizes, the number of currently valid bytes (the blobs infalte
and deflate) and what type of blob it is (see below for the types).

typedef struct BSDir {
    uint16_t  rev;
    uint32_t  checksum; 
    uint32_t  num_entries;
    BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
} __attribute__((packed)) BSDir;


Their worst case sizes have been calculated and according to these sizes
the blobs are written at certain offsets into the blockstorage. Their offsets
are all aligned to sectors (512 byte boundaries).

The TPM provides three different blobs that are written into the storage:

- volatile state
- permanent state
- save state

The 'save state' is written when the VM suspends (ACPI S3) and read when it
resumes. This is done in concert with the BIOS where the BIOS needs to send
a command to the TPM upon resume (TPM_Startup(ST_STATE)), while the OS
issues the command TPM_SaveState().

The 'perment state' is written when the TPM receives a command that alters
its permenent state, i.e., when the a key is loaded into the TPM that
is expected to be there upon reboot of the machine / VM.

Volatile state is written when the frontend triggers it to do so, i.e.,
when the VM's state is written out during taking of a snapshot, migration
or suspension to disk (as in 'virsh save'). This state serves to resume
at the point where the TPM previously stopped but there is no need for it
after a machine reboot for example.

Tricky parts here are related to encrypted storage where certain operations
need to be deferred since the key for the storage only becomes available
much later than the time that the backend is instantiated.

The backend also tries to check for the validity of the block storage for
example. If the Qcow2 is not encrypted and the checksum is found to be
bad, the block storage directory will be initialized. 
In case the Qcow2 is encrypted, initialization will only be done if
the directory is found to be all 0s. In case the directory cannot be
checksummed correctly, but is not all 0s, it is assumed that the user
provided a wrong key. In this case I am not exiting qemu, but black-out
the TPM interface (returns 0xff in all memory location) due to a presumed
fatal error and let the VM run (without TPM functionality).


Signed-off-by: Stefan Berger <address@hidden>

---
 Makefile.target  |    5 
 configure        |    3 
 hw/tpm_builtin.c | 1253 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/tpm_tis.c     |    3 
 4 files changed, 1264 insertions(+)

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_builtin.c
@@ -0,0 +1,1253 @@
+/*
+ *  built-in TPM support using libtpms
+ *
+ *  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 <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 "blockdev.h"
+#include "block_int.h"
+#include "qemu-common.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/pc.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'
+
+#define ALIGN(VAL, SIZE) \
+  ( ( (VAL) + (SIZE) - 1 ) & ~( (SIZE) - 1 ) )
+
+
+#define DIRECTORY_SIZE        BDRV_SECTOR_SIZE
+
+#define PERMSTATE_DISK_OFFSET ALIGN(DIRECTORY_SIZE, BDRV_SECTOR_SIZE)
+#define PERMSTATE_DISK_SPACE \
+              ALIGN(tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE),\
+                    BDRV_SECTOR_SIZE)
+#define SAVESTATE_DISK_OFFSET (PERMSTATE_DISK_OFFSET + PERMSTATE_DISK_SPACE)
+#define SAVESTATE_DISK_SPACE \
+              ALIGN(tpmlib_get_prop(TPMPROP_TPM_MAX_SAVESTATE_SPACE),\
+                    BDRV_SECTOR_SIZE)
+#define VOLASTATE_DISK_OFFSET (SAVESTATE_DISK_OFFSET + SAVESTATE_DISK_SPACE)
+#define VOLASTATE_DISK_SPACE \
+              ALIGN(tpmlib_get_prop(TPMPROP_TPM_MAX_VOLATILESTATE_SPACE),\
+                    BDRV_SECTOR_SIZE)
+
+# define MINIMUM_BS_SIZE       ALIGN(ALIGN(VOLASTATE_DISK_OFFSET +\
+                                           VOLASTATE_DISK_SPACE,  \
+                                           BDRV_SECTOR_SIZE),     \
+                                     1024)
+
+#define MINIMUM_BS_SIZE_KB    (int)(MINIMUM_BS_SIZE / 1024)
+
+
+/* data structures */
+
+typedef struct ThreadParams {
+    TPMState *tpm_state;
+
+    TPMRecvDataCB *recv_data_callback;
+} ThreadParams;
+
+
+enum BSEntryType {
+    BS_ENTRY_PERMSTATE,
+    BS_ENTRY_SAVESTATE,
+    BS_ENTRY_VOLASTATE,
+};
+
+
+typedef struct BSEntry {
+    enum BSEntryType type;
+    int64_t  offset;
+    uint32_t space;
+    uint32_t blobsize;
+} __attribute__((packed)) BSEntry;
+
+
+#define BS_DIR_MAX_NUM_ENTRIES    3  /* permanent, volatile savestate */
+
+typedef struct BSDir {
+    uint16_t  rev;
+    uint32_t  checksum;
+    uint32_t  num_entries;
+    BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
+} __attribute__((packed)) BSDir;
+
+
+#define BS_DIR_REV1         1
+
+#define BS_DIR_REV_CURRENT  BS_DIR_REV1
+
+/* local variables */
+
+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;
+static BlockDriverState *bs;
+
+/* 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 int tpmlib_get_prop(enum TPMLIB_TPMProperty prop)
+{
+    int result;
+
+    TPM_RESULT res = TPMLIB_GetTPMProperty(prop, &result);
+
+    assert(res == TPM_SUCCESS);
+
+    return result;
+}
+
+
+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;
+}
+
+
+/************************************************
+    Block Storage interaction
+ ***********************************************/
+static int find_bs_entry_idx(BSDir *dir, enum BSEntryType type)
+{
+    int c;
+
+    for (c = 0; c < dir->num_entries; c++)
+        if (dir->entries[c].type == type)
+            return c;
+
+    return -ENOENT;
+}
+
+
+static unsigned int sizeof_bsdir(BSDir *dir)
+{
+    return offsetof(BSDir, entries) +
+           dir->num_entries * sizeof(BSEntry);
+}
+
+
+static uint32_t calc_dir_checksum(BSDir *dir)
+{
+    uint16_t checksum, orig;
+
+    orig = dir->checksum;
+    dir->checksum = 0;
+
+    checksum = memsum((unsigned char *)dir, sizeof_bsdir(dir));
+
+    dir->checksum = orig;
+
+    return checksum;
+}
+
+
+static bool is_valid_bsdir(BSDir *dir)
+{
+    if (dir->rev != BS_DIR_REV_CURRENT ||
+        dir->num_entries > BS_DIR_MAX_NUM_ENTRIES)
+        return false;
+    return (dir->checksum == calc_dir_checksum(dir));
+}
+
+
+static int create_blank_dir(BlockDriverState *bs)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+
+    memset(buf, 0x0, sizeof(buf));
+
+    dir = (BSDir *)buf;
+    dir->rev = BS_DIR_REV_CURRENT;
+    dir->num_entries = 0;
+
+    dir->checksum = calc_dir_checksum(dir);
+
+    if (bdrv_write(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    return 0;
+}
+
+
+/**
+ * Validate the block storage doing some basic tests. That's
+ * all that can be done at this point since we don't have the
+ * key yet in case it is encrypted.
+ */
+static int check_bs(BlockDriverState *bs)
+{
+    int64_t len;
+    char buf[20];
+
+    if (!bs) {
+        fprintf(stderr, "Need a block driver for this vTPM type.\n");
+        goto err_exit;
+    }
+
+    len = bdrv_getlength(bs);
+    if (len < MINIMUM_BS_SIZE) {
+        fprintf(stderr, "Required size for vTPM backing store is %dkb\n",
+                        MINIMUM_BS_SIZE_KB);
+        goto err_exit;
+    }
+
+    bdrv_get_format(bs, buf, sizeof(buf));
+    if (strcmp(buf, "qcow2")) {
+        fprintf(stderr, "vTPM backing store must be of type qcow2\n");
+        goto err_exit;
+    }
+
+    return 0;
+
+ err_exit:
+    fprintf(stderr,
+            "Create the drive using 'qemu-img create -f qcow2 "
+            "<filename> %dk'\n", MINIMUM_BS_SIZE_KB);
+    return -EFAULT;
+}
+
+
+/*
+ * Startup the block storage: read the directory and check whether its
+ * checksum is valid. If the checksum is not valid then
+ *
+ * - if the block storage is not encrypted initialize it assuming it's
+ *   been freshly created or corrupted
+ *
+ * - if the block storage is encrypted
+ *     - check whether it's been freshly created (expecting a 0 sum of the
+ *       directory; seems to work with any key) and initialize it in that case
+ *     - otherwise, if there are some unreadable data, assume that
+ *       the wrong key was given and mark it as a starup error. We log it
+ *       but won't exit() here.
+ */
+static int startup_bs(BlockDriverState *bs)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+
+    if (bdrv_read(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    dir = (BSDir *)buf;
+
+    if (!is_valid_bsdir(dir)) {
+        /* if it's encrypted and has something else than null-content,
+           we assume to have the wrong key */
+        if (bdrv_is_encrypted(bs)) {
+            if (memsum(buf, sizeof(buf)) != 0) {
+                fprintf(stderr,
+                        "vTPM block storage directory is not valid. "
+                        "Assuming the key is wrong.\n");
+                had_startup_error = true;
+                return 1;
+            }
+        }
+#ifdef DEBUG_TPM
+        fprintf(stderr, "*** tpm: Blanking the storage directory.\n");
+#endif
+        return create_blank_dir(bs);
+    }
+
+    return 0;
+}
+
+
+static int create_bs_entry(BlockDriverState *bs,
+                           BSDir *dir,
+                           enum BSEntryType type,
+                           uint32_t blobsize)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    uint32_t idx = dir->num_entries++;
+    uint32_t space;
+
+    dir->entries[idx].offset = (idx == 0)
+        ? ALIGN(DIRECTORY_SIZE, BDRV_SECTOR_SIZE)
+        : dir->entries[idx-1].offset + ALIGN(dir->entries[idx-1].space,
+                                             BDRV_SECTOR_SIZE);
+
+    dir->entries[idx].type = type;
+
+    switch (type) {
+    case BS_ENTRY_PERMSTATE:
+        space = PERMSTATE_DISK_SPACE;
+        break;
+    case BS_ENTRY_SAVESTATE:
+        space = SAVESTATE_DISK_SPACE;
+        break;
+    case BS_ENTRY_VOLASTATE:
+        space = VOLASTATE_DISK_SPACE;
+        break;
+    default:
+        assert(false);
+    }
+
+    dir->entries[idx].space = space;
+    dir->entries[idx].blobsize = blobsize;
+
+    dir->checksum = calc_dir_checksum(dir);
+
+    assert(space >= blobsize);
+
+    memset(buf, 0x0, sizeof(buf));
+    memcpy(buf, dir, sizeof_bsdir(dir));
+
+    if (bdrv_write(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    return idx;
+}
+
+
+static int get_bs_entry(BlockDriverState *bs,
+                        enum BSEntryType type,
+                        BSEntry *entry)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+    int idx;
+
+    if (bdrv_read(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    dir = (BSDir *)buf;
+
+    assert(is_valid_bsdir(dir));
+
+    if ((idx = find_bs_entry_idx(dir, type)) < 0) {
+        if ((idx = create_bs_entry(bs, dir, type, 0)) < 0)
+            return -EIO;
+    }
+
+    memcpy(entry, &dir->entries[idx], sizeof(*entry));
+
+    return 0;
+}
+
+
+static int set_bs_entry_size(BlockDriverState *bs,
+                             enum BSEntryType type,
+                             BSEntry *entry,
+                             uint32_t blobsize)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+    int idx;
+
+    if (bdrv_read(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    dir = (BSDir *)buf;
+
+    assert(is_valid_bsdir(dir));
+
+    if ((idx = find_bs_entry_idx(dir, type)) < 0) {
+        if ((idx = create_bs_entry(bs, dir, type, 0)) < 0)
+            return -EIO;
+    }
+
+    assert(blobsize <= dir->entries[idx].space);
+    dir->entries[idx].blobsize = blobsize;
+
+    dir->checksum = calc_dir_checksum(dir);
+
+    if (bdrv_write(bs, 0, buf, 1) < 0)
+        return -EIO;
+
+    memcpy(entry, &dir->entries[idx], sizeof(*entry));
+
+    return 0;
+}
+
+
+static int load_sized_data_from_bs(BlockDriverState *bs,
+                                   enum BSEntryType be,
+                                   TPMSizedBuffer *tsb)
+{
+    BSEntry entry;
+    int n;
+
+    if ((n = get_bs_entry(bs, be, &entry)) < 0)
+        return n;
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr,"load: be-type: %d, offset: %ld, size: %d\n",
+            be, entry.offset, entry.blobsize);
+#endif
+
+    if (entry.blobsize == 0)
+        return 0;
+
+    tsb->buffer = qemu_malloc(entry.blobsize);
+    if (!tsb->buffer)
+        return -ENOMEM;
+
+    tsb->size = entry.blobsize;
+
+    if (bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size) != tsb->size) {
+        clear_sized_buffer(tsb);
+        fprintf(stderr,"tpm: Error while reading sized data!\n");
+        return -EIO;
+    }
+
+    return 0;
+}
+
+
+static int load_tpm_permanent_state_from_bs(BlockDriverState *bs,
+                                            TPMSizedBuffer *tsb)
+{
+    return load_sized_data_from_bs(bs, BS_ENTRY_PERMSTATE, tsb);
+}
+
+
+static int load_tpm_savestate_from_bs(BlockDriverState *bs,
+                                      TPMSizedBuffer *tsb)
+{
+    return load_sized_data_from_bs(bs, BS_ENTRY_SAVESTATE, tsb);
+}
+
+
+static int load_tpm_volatile_state_from_bs(BlockDriverState *bs,
+                                           TPMSizedBuffer *tsb)
+{
+    return load_sized_data_from_bs(bs, BS_ENTRY_VOLASTATE, tsb);
+}
+
+
+static int save_sized_data_to_bs(BlockDriverState *bs,
+                                 enum BSEntryType be,
+                                 uint8_t *data, uint32_t data_len)
+{
+    BSEntry entry;
+    int n;
+
+    if ((n = set_bs_entry_size(bs, be, &entry, data_len)) < 0)
+        return n;
+
+    if (data_len > 0) {
+        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len)
+            return -EIO;
+    }
+
+    return 0;
+}
+
+
+/* Write the TPM's state to block storage */
+static int sync_permanent_state_to_disk(BlockDriverState *bs)
+{
+    int rc = 0;
+
+    if (permanent_state.size)
+        rc = save_sized_data_to_bs(bs, BS_ENTRY_PERMSTATE,
+                                   permanent_state.buffer,
+                                   permanent_state.size);
+
+    return rc;
+}
+
+
+static int sync_savestate_to_disk(BlockDriverState *bs)
+{
+    return save_sized_data_to_bs(bs, BS_ENTRY_SAVESTATE,
+                                 save_state.buffer, save_state.size);
+}
+
+
+static int sync_volatile_state_to_disk(BlockDriverState *bs)
+{
+    return save_sized_data_to_bs(bs, BS_ENTRY_VOLASTATE,
+                                 volatile_state.buffer, volatile_state.size);
+}
+
+
+/*
+ * Write a given type of state, identified by the char, to block
+ * storage. If anything goes wrong, set the had_fatal_error variable
+ */
+static int write_state_to_bs(char what)
+{
+    int rc = 0;
+
+    qemu_mutex_lock(&state_mutex);
+
+    switch (what) {
+    case PERMSTATE_TYPE:
+        rc = sync_permanent_state_to_disk(bs);
+        break;
+    case SAVESTATE_TYPE:
+        rc = sync_savestate_to_disk(bs);
+        break;
+    case VOLASTATE_TYPE:
+        rc = sync_volatile_state_to_disk(bs);
+        break;
+    default:
+        assert(false);
+    }
+
+    if (rc) {
+        fprintf(stderr,"tpm: Error while writing TPM state to bs. "
+                       "Setting fatal error.");
+        had_fatal_error = true;
+    }
+
+    qemu_mutex_unlock(&state_mutex);
+
+    return rc;
+}
+
+
+/*
+ * Write the 'savestate' or 'permanent state' in the
+ * global buffer to disk. The requester tells us what
+ * to writy by a single byte in the pipe. If anything
+ * goes wrong, we'll set the had_fatal_error flag.
+ * We sync with the requester using signals on a
+ * condition.
+ */
+static void fulfill_sync_to_bs_request(void *opaque)
+{
+    char buf[10];
+    int c, n;
+
+    while ((n = read(pipefd[0], buf, sizeof(buf))) > 0) {
+        for (c = 0; c < n; c++)
+            write_state_to_bs(buf[c]);
+    }
+
+    qemu_cond_signal(&bs_write_result_cond);
+}
+
+
+/*
+ * Request that either savestate or permanent state be written
+ * to the disk. Call this function with the state_mutex held.
+ * It will synchronize with the sync_to_bs function that does
+ * the work. In case a previous fatal error occurred, nothing
+ * will be done.
+ */
+static bool request_sync_to_bs(char what)
+{
+    char cmd[1] = { what };
+
+    if (had_fatal_error)
+        return had_fatal_error;
+
+    if (write(pipefd[1], cmd, 1) != 1) {
+        had_fatal_error = true;
+        return true;
+    }
+
+    if (tpm_initialized) {
+        qemu_cond_wait(&bs_write_result_cond, &state_mutex);
+    } else {
+        /* during initialization: defer the write */
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr,"tpm: deferred write/sync since not in thread\n");
+#endif
+    }
+
+    return had_fatal_error;
+}
+
+
+static void load_tpm_state_from_bs(BlockDriverState *bs)
+{
+    load_tpm_permanent_state_from_bs(bs, &permanent_state);
+    load_tpm_savestate_from_bs(bs, &save_state);
+
+    if (need_read_volatile) {
+        clear_sized_buffer(&volatile_state);
+        load_tpm_volatile_state_from_bs(bs, &volatile_state);
+        need_read_volatile = false;
+    }
+}
+
+
+/**
+ * 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
+    fprintf(stderr,"tpm: *** tpm startup was successful! ***\n");
+#endif
+
+    return 0;
+}
+
+
+/*
+ * 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 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;
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static void terminate_tpm_thread(void)
+{
+    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);
+
+        qemu_thread_join(&thread, NULL);
+        memset(&thread, 0, sizeof(thread));
+
+        if (tpm_initialized) {
+            TPMLIB_Terminate();
+            tpm_initialized = false;
+        }
+    }
+}
+
+
+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)
+{
+    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 */
+    while (!thread_terminate) {
+        /* receive and handle commands */
+        in_len = 0;
+        do {
+#ifdef DEBUG_TPM
+            fprintf(stderr,"waiting for commands...\n");
+#endif
+
+            if (thread_terminate)
+                break;
+
+            qemu_mutex_lock(&tParams->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 (!tParams->tpm_state->to_tpm_execute)
+                qemu_cond_wait(&tParams->tpm_state->to_tpm_cond,
+                               &tParams->tpm_state->state_lock);
+
+            tParams->tpm_state->to_tpm_execute = false;
+
+            qemu_mutex_unlock(&tParams->tpm_state->state_lock);
+
+            if (thread_terminate)
+                break;
+
+            g_locty = tParams->tpm_state->command_locty;
+
+            in = tParams->tpm_state->loc[g_locty].w_buffer.buffer;
+            in_len = tParams->tpm_state->loc[g_locty].w_offset;
+
+            if (!had_fatal_error) {
+
+                out_len = tParams->tpm_state->loc[g_locty].r_buffer.size;
+
+#ifdef DEBUG_TPM
+                fprintf(stderr,
+                        "tpm: received %d bytes from VM in locality %d\n",
+                        in_len,
+                        g_locty);
+                dumpBuffer(stdout, in, in_len);
+#endif
+
+                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);
+
+                /* TPMLIB_Process must not grow the buffer */
+                assert(out_len ==
+                       tParams->tpm_state->loc[g_locty].r_buffer.size);
+
+                if (res != TPM_SUCCESS) {
+#ifdef DEBUG_TPM
+                    fprintf(stderr,"TPMLIB_Process() failed\n");
+#endif
+                    had_fatal_error = 1;
+                }
+            }
+
+            if (had_fatal_error) {
+                out = tParams->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,"sending %d bytes to VM\n", resp_size);
+            dumpBuffer(stdout, out, resp_size);
+#endif
+            tParams->recv_data_callback(tParams->tpm_state, g_locty);
+        } while (in_len > 0);
+    }
+
+    return NULL;
+}
+
+
+/*****************************************************************
+ * 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 %d BYTES OF PERMANENT ALL\n",
+                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 %d BYTES OF SAVESTATE\n",
+                length);
+#endif
+        what = SAVESTATE_TYPE;
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    } else if (!strcmp(name, TPM_VOLATILESTATE_NAME)) {
+        fprintf(stderr,"tpm: STORING %d BYTES OF VOLATILE STATE\n",
+                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;
+}
+
+
+/*****************************************************************/
+
+
+static void reset(void)
+{
+    static bool thread_running;
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr,"tpm: CALL TO TPM_RESET!\n");
+#endif
+
+    if (thread_running) {
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+        fprintf(stderr,"tpm: TERMINATING RUNNING TPM THREAD\n");
+#endif
+        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);
+    thread_running = true;
+}
+
+
+/*
+ * 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 tpm_initialized variable is 'true'.
+ *
+ */
+static int instantiate_with_volatile_data(TPMState *s)
+{
+    if (tpm_initialized) {
+#ifdef DEBUG_TPM_SR
+        fprintf(stderr,"tpm: This is resume of a SNAPSHOT?!\n");
+#endif
+        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);
+
+    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)
+{
+    TPM_BOOL tpmEstablished = false;
+
+    if (tpm_initialized)
+        TPM_IO_TpmEstablished_Get(&tpmEstablished);
+
+    return (bool)tpmEstablished;
+}
+
+
+static bool 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. 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.
+         */
+        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);
+#ifdef DEBUG_TPM_SR
+    fprintf(stderr,"tpm: got %d bytes of volatilestate\n", buflen);
+#endif
+
+    if (res != TPM_SUCCESS) {
+#ifdef DEBUG_TPM_SR
+        fprintf(stderr,"tpm: Error: Could not store TPM state\n");
+#endif
+        return 1;
+    }
+
+    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)
+{
+    TPM_RESULT res;
+    size_t wanted_size = tpmlib_get_prop(TPMPROP_TPM_BUFFER_MAX);
+
+    if (sb->size != wanted_size) {
+        res = TPM_Realloc(&sb->buffer, wanted_size);
+        if (res == TPM_SUCCESS)
+            sb->size = wanted_size;
+        else
+            sb->size = 0;
+    }
+    return sb->size;
+}
+
+
+static const char *create_desc(void)
+{
+    static int done;
+
+    if (!done) {
+        snprintf(dev_description, sizeof(dev_description),
+                 "Qemu's built-in TPM; requires %ukb of block storage",
+                 MINIMUM_BS_SIZE_KB);
+        done = 1;
+    }
+
+    return dev_description;
+}
+
+
+#define TPM_OPTS "id=vtpm-nvram"
+
+static bool handle_options(QemuOpts *opts)
+{
+    const char *value;
+
+    value = qemu_opt_get(opts, "path");
+    if (value) {
+        drive_add(IF_NONE, -1, value, TPM_OPTS);
+    } else {
+        fprintf(stderr,"-tpm is missing path= parameter\n");
+        return false;
+    }
+    return true;
+}
+
+
+struct backend_tpm_driver builtin = {
+    .id                             = "builtin",
+    .desc                           = create_desc,
+    .handle_options                 = handle_options,
+    .init                           = init,
+    .late_startup_tpm               = late_startup_tpm,
+    .realloc_buffer                 = realloc_buffer,
+    .reset                          = reset,
+    .had_startup_error              = get_startup_error,
+    .save_volatile_data             = save_volatile_data,
+    .load_volatile_data             = instantiate_with_volatile_data,
+    .get_tpm_established_flag       = get_tpm_established_flag,
+};
Index: qemu-git/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -294,6 +294,11 @@ obj-sparc-y += grlib_gptimer.o grlib_irq
 ifeq ($(TARGET_ARCH),$(filter $(TARGET_ARCH),i386 x86_64))
 
 obj-i386-$(CONFIG_TPM) += tpm_tis.o qemu-thread.o
+obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
+
+ifdef CONFIG_TPM_BUILTIN
+LIBS+=-ltpms
+endif
 
 endif
 
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -3319,6 +3319,9 @@ if test "$tpm" = "yes"; then
   fi
 
   if test "$has_tpm" = "1"; then
+      if test -r /usr/include/libtpms/tpm_library.h ; then
+          echo "CONFIG_TPM_BUILTIN=y" >> $config_target_mak
+      fi
       echo "CONFIG_TPM=y" >> $config_host_mak
   fi
 fi
Index: qemu-git/hw/tpm_tis.c
===================================================================
--- qemu-git.orig/hw/tpm_tis.c
+++ qemu-git/hw/tpm_tis.c
@@ -94,6 +94,9 @@ static uint32_t tis_mem_readl(void *opaq
 
 
 static const struct backend_tpm_driver *bes[] = {
+#ifdef CONFIG_TPM_BUILTIN
+    &builtin,
+#endif
     NULL,
 };
 




reply via email to

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