From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([140.186.70.92]:43828) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QIOty-0006fO-5M for qemu-devel@nongnu.org; Fri, 06 May 2011 13:33:20 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1QIOtv-0003rO-IR for qemu-devel@nongnu.org; Fri, 06 May 2011 13:33:18 -0400 Received: from e35.co.us.ibm.com ([32.97.110.153]:50340) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1QIOtv-0003rG-4C for qemu-devel@nongnu.org; Fri, 06 May 2011 13:33:15 -0400 Received: from d03relay05.boulder.ibm.com (d03relay05.boulder.ibm.com [9.17.195.107]) by e35.co.us.ibm.com (8.14.4/8.13.1) with ESMTP id p46HGRqD026234 for ; Fri, 6 May 2011 11:16:27 -0600 Received: from d03av02.boulder.ibm.com (d03av02.boulder.ibm.com [9.17.195.168]) by d03relay05.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id p46HWoSg202176 for ; Fri, 6 May 2011 11:33:03 -0600 Received: from d03av02.boulder.ibm.com (loopback [127.0.0.1]) by d03av02.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id p46BWLGV004561 for ; Fri, 6 May 2011 05:32:21 -0600 Message-Id: <20110506173247.129923582@linux.vnet.ibm.com> Date: Fri, 06 May 2011 13:32:31 -0400 From: Stefan Berger References: <20110506173224.278066589@linux.vnet.ibm.com> Content-Disposition: inline; filename=qemu_tpm_backend.diff Subject: [Qemu-devel] [PATCH V4 07/10] Implementation of the libtpms-based backend List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: stefanb@linux.vnet.ibm.com, qemu-devel@nongnu.org Cc: andreas.niederl@iaik.tugraz.at, serge@hallyn.com 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. v3: - temporarily deactivate the building of the tpm_builtin.c until subsequent patch completely converts it to the libtpms based driver v2: - fixes to adhere to the qemu coding style Signed-off-by: Stefan Berger --- configure | 1 hw/tpm_builtin.c | 415 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- hw/tpm_tis.h | 17 ++ 3 files changed, 412 insertions(+), 21 deletions(-) Index: qemu-git/hw/tpm_tis.h =================================================================== --- qemu-git.orig/hw/tpm_tis.h +++ qemu-git/hw/tpm_tis.h @@ -142,4 +142,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,5 +1,5 @@ /* - * builtin 'null' TPM driver + * builtin TPM driver based on libtpms * * Copyright (c) 2010, 2011 IBM Corporation * Copyright (c) 2010, 2011 Stefan Berger @@ -18,16 +18,32 @@ * License along with this library; if not, see */ +#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 "migration.h" +#include "sysemu.h" + +#include +#include +#include +#include +#include + +#include //#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 { @@ -43,12 +59,18 @@ static QemuThread thread; static QemuMutex state_mutex; /* protects *_state below */ static QemuMutex tpm_initialized_mutex; /* protect tpm_initialized */ +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 thread_running = false; +static bool need_read_volatile = false; static ThreadParams tpm_thread_params; @@ -62,9 +84,21 @@ 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; +} + + static void *tpm_builtin_main_loop(void *d) { - int res = 1; + TPM_RESULT res; ThreadParams *thr_parms = d; uint32_t in_len, out_len; uint8_t *in, *out; @@ -74,9 +108,10 @@ static void *tpm_builtin_main_loop(void fprintf(stderr, "tpm: THREAD IS STARTING\n"); #endif - if (res != 0) { + res = TPMLIB_MainInit(); + if (res != TPM_SUCCESS) { #if defined DEBUG_TPM || defined DEBUG_TPM_SR - fprintf(stderr, "tpm: Error: TPM initialization failed (rc=%d)\n", + fprintf(stderr," tpm: Error: Call to TPMLIB_MainInit() failed (rc=%d)\n", res); #endif had_fatal_error = true; @@ -94,6 +129,8 @@ static void *tpm_builtin_main_loop(void in_len = 0; do { #ifdef DEBUG_TPM + /* TPMLIB output goes to stderr */ + fflush(stdout); fprintf(stderr, "tpm: waiting for commands...\n"); #endif @@ -137,13 +174,19 @@ static void *tpm_builtin_main_loop(void resp_size = 0; - /* !!! Send command to TPM & wait for response */ + /* TPMLIB_Process may realloc the response buffer */ + res = TPMLIB_Process( + &thr_parms->tpm_state->loc[g_locty].r_buffer.buffer, + &resp_size, &out_len, + in, in_len); - if (res != 0) { #ifdef DEBUG_TPM - fprintf(stderr, - "tpm: Sending/receiving TPM request/response " - "failed.\n"); + fflush(stdout); +#endif + + if (res != TPM_SUCCESS) { +#ifdef DEBUG_TPM + fprintf(stderr, "tpm: TPMLIB_Process() failed\n"); #endif had_fatal_error = 1; } @@ -171,11 +214,13 @@ static void *tpm_builtin_main_loop(void if (tpm_initialized) { tpm_initialized = false; + TPMLIB_Terminate(); } qemu_mutex_unlock(&tpm_initialized_mutex); #ifdef DEBUG_TPM + fflush(stdout); fprintf(stderr, "tpm: THREAD IS ENDING\n"); #endif @@ -206,7 +251,7 @@ static void tpm_builtin_terminate_tpm_th * still ask us to write data to the disk, though. */ while (thread_running) { - /* !!! write data to disk if necessary */ + tpm_builtin_fulfill_sync_to_bs_request(NULL); usleep(100000); } @@ -218,6 +263,12 @@ static void tpm_builtin_terminate_tpm_th static void tpm_builtin_tpm_atexit(void) { tpm_builtin_terminate_tpm_thread(); + + close(pipefd[0]); + pipefd[0] = -1; + + close(pipefd[1]); + pipefd[1] = -1; } @@ -241,8 +292,17 @@ static int tpm_builtin_startup_tpm(void) } -static int tpm_builtin_do_startup_tpm(void) +static int tpm_builtin_do_startup_tpm(bool fail_on_encrypted_drive) { + int rc; + + rc = startup_bs(bs, fail_on_encrypted_drive); + if (rc) { + return rc; + } + + load_tpm_state_from_bs(bs); + return tpm_builtin_startup_tpm(); } @@ -253,7 +313,7 @@ static int tpm_builtin_do_startup_tpm(vo */ static int tpm_builtin_early_startup_tpm(void) { - return tpm_builtin_do_startup_tpm(); + return tpm_builtin_do_startup_tpm(true); } @@ -269,7 +329,7 @@ static int tpm_builtin_late_startup_tpm( had_fatal_error = false; had_startup_error = false; - if (tpm_builtin_do_startup_tpm()) { + if (tpm_builtin_do_startup_tpm(false)) { had_fatal_error = true; } @@ -277,6 +337,213 @@ static int tpm_builtin_late_startup_tpm( } +/***************************************************************** + * call back functions for the libtpms TPM library + ****************************************************************/ +static TPM_RESULT tpm_builtin_nvram_init(void) +{ + return TPM_SUCCESS; +} + + +static TPM_RESULT tpm_builtin_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 [crc=%08x]; rc = %d\n", + *length, (int)crc32(0, *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_builtin_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 (incoming_expected) { +#if defined DEBUG_TPM || defined DEBUG_TPM_SR + fprintf(stderr, "tpm: Not storing data due to incoming migration\n"); +#endif + return TPM_SUCCESS; + } + + 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 [crc=%08x]\n", + length, (int)crc32(0, 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 [crc=%08x]\n", + length, (int)crc32(0, 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 [crc=%08x]\n", + length, (int)crc32(0, data, length)); +#endif + } + + if (tsb) { + 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_builtin_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_builtin_io_init(void) +{ + return TPM_SUCCESS; +} + + +static TPM_RESULT tpm_builtin_io_getlocality( + TPM_MODIFIER_INDICATOR *localityModifier) +{ + *localityModifier = (TPM_MODIFIER_INDICATOR)g_locty; + + return TPM_SUCCESS; +} + + +static TPM_RESULT tpm_builtin_io_getphysicalpresence( + TPM_BOOL *physicalPresence) +{ + *physicalPresence = FALSE; + + return TPM_SUCCESS; +} + + +/*****************************************************************/ + + static void tpm_builtin_reset(void) { #if defined DEBUG_TPM || defined DEBUG_TPM_SR @@ -285,7 +552,11 @@ static void tpm_builtin_reset(void) tpm_builtin_terminate_tpm_thread(); + clear_sized_buffer(&permanent_state); + clear_sized_buffer(&save_state); + clear_sized_buffer(&volatile_state); had_fatal_error = false; + need_read_volatile = false; had_startup_error = false; } @@ -316,27 +587,95 @@ static int tpm_builtin_instantiate_with_ 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_builtin_nvram_init, + .tpm_nvram_loaddata = tpm_builtin_nvram_loaddata, + .tpm_nvram_storedata = tpm_builtin_nvram_storedata, + .tpm_nvram_deletename = tpm_builtin_nvram_deletename, + .tpm_io_init = tpm_builtin_io_init, + .tpm_io_getlocality = tpm_builtin_io_getlocality, + .tpm_io_getphysicalpresence = tpm_builtin_io_getphysicalpresence, +}; + + static int tpm_builtin_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_mutex_init(&tpm_initialized_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], tpm_builtin_fulfill_sync_to_bs_request, + NULL, NULL); atexit(tpm_builtin_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 tpm_builtin_get_tpm_established_flag(void) { - return false; + TPM_BOOL tpmEstablished = false; + + qemu_mutex_lock(&tpm_initialized_mutex); + + if (tpm_initialized) { + TPM_IO_TpmEstablished_Get(&tpmEstablished); + } + + qemu_mutex_unlock(&tpm_initialized_mutex); + + return (bool)tpmEstablished; } @@ -352,6 +691,10 @@ static bool tpm_builtin_get_startup_erro */ static int tpm_builtin_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. @@ -359,17 +702,46 @@ static int tpm_builtin_save_volatile_dat 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 [crc=%08x]\n", + buflen, (int)crc32(0, 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 */ + tpm_builtin_fulfill_sync_to_bs_request(NULL); + return 0; } static size_t tpm_builtin_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 { sb->size = 0; @@ -385,7 +757,8 @@ static const char *tpm_builtin_create_de 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; } @@ -393,13 +766,15 @@ static const char *tpm_builtin_create_de } +#define TPM_OPTS "id=vtpm-nvram" + static bool tpm_builtin_handle_options(QemuOpts *opts) { const char *value; value = qemu_opt_get(opts, "path"); if (value) { - /* !!! handle file path */ + drive_add(IF_NONE, -1, value, TPM_OPTS); } else { fprintf(stderr, "-tpm is missing path= parameter\n"); return false; @@ -411,7 +786,7 @@ static bool tpm_builtin_handle_options(Q BackendTPMDriver tpm_builtin = { .id = "builtin", .desc = tpm_builtin_create_desc, - .job_for_main_thread = NULL, + .job_for_main_thread = tpm_builtin_fulfill_sync_to_bs_request, .handle_options = tpm_builtin_handle_options, .init = tpm_builtin_init, .early_startup_tpm = tpm_builtin_early_startup_tpm, Index: qemu-git/configure =================================================================== --- qemu-git.orig/configure +++ qemu-git/configure @@ -3449,7 +3449,6 @@ 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