This patch adds a TPM null driver implementation acting as a backend for the TIS hardware emulation. The NULL driver responds to all commands with a TPM fault response.
To use this null driver, use either -tpm null or -tpmdev null,id=tpm0 -device tpm-tis,tpmdev=tpm0 as parameters on the command line. If TPM support is chosen via './configure --enable-tpm ...' TPM support is now always compiled into Qemu and at least the null driver will be available on emulators for x86_64 and i386. v8: - initializing 'in' variable Signed-off-by: Stefan Berger <stef...@linux.vnet.ibm.com> --- Makefile.target | 2 configure | 8 - hw/tpm_null.c | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-options.hx | 13 +- tpm.c | 1 tpm.h | 1 6 files changed, 341 insertions(+), 11 deletions(-) Index: qemu-git/hw/tpm_null.c =================================================================== --- /dev/null +++ qemu-git/hw/tpm_null.c @@ -0,0 +1,327 @@ +/* + * 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 bool thread_terminate; +static bool thread_running; + +static ThreadParams tpm_thread_params; + +static const unsigned char tpm_std_fatal_error_response[10] = { + 0x00, 0xc4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09 /* TPM_FAIL */ +}; + +static char dev_description[80]; + + +static void *tpm_null_main_loop(void *d) +{ + ThreadParams *thr_parms = d; + uint32_t in_len; + uint8_t *in, *out; + uint8_t locty; + +#ifdef DEBUG_TPM + fprintf(stderr, "tpm: THREAD IS STARTING\n"); +#endif + + /* start command processing */ + while (!thread_terminate) { + /* receive and handle commands */ + in_len = 0; + do { +#ifdef DEBUG_TPM + fprintf(stderr, "tpm: waiting for commands...\n"); +#endif + + if (thread_terminate) { + break; + } + + qemu_mutex_lock(&thr_parms->tpm_state->state_lock); + + /* in case we were to slow and missed the signal, the + to_tpm_execute boolean tells us about a pending command */ + if (!thr_parms->tpm_state->to_tpm_execute) { + qemu_cond_wait(&thr_parms->tpm_state->to_tpm_cond, + &thr_parms->tpm_state->state_lock); + } + + thr_parms->tpm_state->to_tpm_execute = false; + + qemu_mutex_unlock(&thr_parms->tpm_state->state_lock); + + if (thread_terminate) { + break; + } + + locty = thr_parms->tpm_state->command_locty; + + in = thr_parms->tpm_state->loc[locty].w_buffer.buffer; + in_len = thr_parms->tpm_state->loc[locty].w_offset; + + out = thr_parms->tpm_state->loc[locty].r_buffer.buffer; + + memcpy(out, tpm_std_fatal_error_response, + sizeof(tpm_std_fatal_error_response)); + + out[1] = (in_len > 2 && in[1] >= 0xc1 && in[1] <= 0xc3) + ? in[1] + 3 + : 0xc4; +#ifdef DEBUG_TPM + fprintf(stderr, "tpm_null: sending fault response to VM\n"); +#endif + thr_parms->recv_data_callback(thr_parms->tpm_state, locty); + } while (in_len > 0); + } + +#ifdef DEBUG_TPM + fprintf(stderr, "tpm: THREAD IS ENDING\n"); +#endif + + thread_running = false; + + return NULL; +} + + +static void tpm_null_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); + + memset(&thread, 0, sizeof(thread)); + } +} + + +static void tpm_null_tpm_atexit(void) +{ + tpm_null_terminate_tpm_thread(); +} + + +/** + * Start the TPM (thread). If it had been started before, then terminate + * and start it again. + */ +static int tpm_null_startup_tpm(void) +{ + /* terminate a running TPM */ + tpm_null_terminate_tpm_thread(); + + /* reset the flag so the thread keeps on running */ + thread_terminate = false; + + qemu_thread_create(&thread, tpm_null_main_loop, &tpm_thread_params); + + thread_running = true; + + return 0; +} + + +static int tpm_null_do_startup_tpm(void) +{ + return tpm_null_startup_tpm(); +} + + +static int tpm_null_early_startup_tpm(void) +{ + return tpm_null_do_startup_tpm(); +} + + +static int tpm_null_late_startup_tpm(void) +{ + return tpm_null_do_startup_tpm(); +} + + +static void tpm_null_reset(void) +{ +#if defined DEBUG_TPM || defined DEBUG_TPM_SR + fprintf(stderr, "tpm: CALL TO TPM_RESET!\n"); +#endif + + tpm_null_terminate_tpm_thread(); +} + + +/* + * Since the null driver does not have much persistent storage + * there is not much to do here... + */ +static int tpm_null_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_null_init(TPMState *s, TPMRecvDataCB *recv_data_cb) +{ + tpm_thread_params.tpm_state = s; + tpm_thread_params.recv_data_callback = recv_data_cb; + + atexit(tpm_null_tpm_atexit); + + return 0; +} + + +static bool tpm_null_get_tpm_established_flag(void) +{ + return false; +} + + +static bool tpm_null_get_startup_error(void) +{ + return false; +} + + +/** + * 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_null_save_volatile_data(void) +{ + return 0; +} + + +static size_t tpm_null_realloc_buffer(TPMSizedBuffer *sb) +{ + size_t wanted_size = 4096; + + if (sb->size != wanted_size) { + sb->buffer = g_realloc(sb->buffer, wanted_size); + if (sb->buffer != NULL) { + sb->size = wanted_size; + } else { + sb->size = 0; + } + } + return sb->size; +} + + +static const char *tpm_null_create_desc(void) +{ + static int done; + + if (!done) { + snprintf(dev_description, sizeof(dev_description), + "Null TPM backend driver"); + done = 1; + } + + return dev_description; +} + + +static TPMBackend *tpm_null_create(QemuOpts *opts, const char *id, + const char *model) +{ + TPMBackend *driver; + + driver = g_malloc(sizeof(TPMBackend)); + if (!driver) { + fprintf(stderr, "Could not allocate memory.\n"); + return NULL; + } + driver->id = g_strdup(id); + if (model) { + driver->model = g_strdup(model); + } + driver->ops = &tpm_null_driver; + + return driver; +} + + +static void tpm_null_destroy(TPMBackend *driver) +{ + g_free(driver->id); + g_free(driver->model); + g_free(driver); +} + + +TPMDriverOps tpm_null_driver = { + .id = "null", + .desc = tpm_null_create_desc, + .job_for_main_thread = NULL, + .create = tpm_null_create, + .destroy = tpm_null_destroy, + .init = tpm_null_init, + .early_startup_tpm = tpm_null_early_startup_tpm, + .late_startup_tpm = tpm_null_late_startup_tpm, + .realloc_buffer = tpm_null_realloc_buffer, + .reset = tpm_null_reset, + .had_startup_error = tpm_null_get_startup_error, + .save_volatile_data = tpm_null_save_volatile_data, + .load_volatile_data = tpm_null_instantiate_with_volatile_data, + .get_tpm_established_flag = tpm_null_get_tpm_established_flag, +}; Index: qemu-git/Makefile.target =================================================================== --- qemu-git.orig/Makefile.target +++ qemu-git/Makefile.target @@ -233,7 +233,7 @@ obj-i386-y += debugcon.o multiboot.o obj-i386-y += pc_piix.o obj-i386-$(CONFIG_KVM) += kvmclock.o obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o -obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o +obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o tpm_null.o obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o ifdef CONFIG_TPM_BUILTIN Index: qemu-git/tpm.c =================================================================== --- qemu-git.orig/tpm.c +++ qemu-git/tpm.c @@ -24,6 +24,7 @@ #if defined(TARGET_I386) || defined(TARGET_X86_64) static const TPMDriverOps *bes[] = { + &tpm_null_driver, #ifdef CONFIG_TPM_BUILTIN &tpm_builtin, #endif Index: qemu-git/tpm.h =================================================================== --- qemu-git.orig/tpm.h +++ qemu-git/tpm.h @@ -141,6 +141,7 @@ void tpm_measure_buffer(const void *buff TPMMeasureType type, uint8_t pcrindex, const void *data, uint32_t data_len); +extern TPMDriverOps tpm_null_driver; extern TPMDriverOps tpm_builtin; #endif /* _HW_TPM_CONFIG_H */ Index: qemu-git/qemu-options.hx =================================================================== --- qemu-git.orig/qemu-options.hx +++ qemu-git/qemu-options.hx @@ -1769,6 +1769,8 @@ DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \ "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \ " enable a builtin TPM with state in file in path\n" \ " and encrypt the TPM's state with the given AES key\n" \ + "-tpm null enable a TPM null driver that responds with a fault\n" \ + " message to every TPM request\n" \ "-tpm model=? to list available TPM device models\n" \ "-tpm ? to list available TPM backend types\n", QEMU_ARCH_I386) @@ -1784,8 +1786,9 @@ The general form of a TPM device option @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}] @findex -tpmdev -Backend type must be: -@option{builtin}. +Backend type must be one of: +@option{builtin}, +@option{null}. The specific backend type will determine the applicable options. The @code{-tpmdev} options requires a @code{-device} option. @@ -1826,6 +1829,12 @@ using AES-CBC encryption scheme supply t @example -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0 @end example + +@item -tpmdev null + +Creates an instance of a TPM null driver that responds to every command +with a fault message. + @end table The short form of a TPM device option is: Index: qemu-git/configure =================================================================== --- qemu-git.orig/configure +++ qemu-git/configure @@ -2593,8 +2593,6 @@ EOF libtpms=no if compile_prog "" "-ltpms" ; then libtpms=yes - else - tpm_need_pkgs="libtpms development package" fi fi @@ -3598,12 +3596,6 @@ if test "$tpm" = "yes"; then if test "$has_tpm" = "1"; then if test "$libtpms" = "yes" ; then echo "CONFIG_TPM_BUILTIN=y" >> $config_target_mak - else - echo - echo "TPM support cannot be added since no TPM backend can be compiled." - echo "Please install the $tpm_need_pkgs." - echo - exit 1 fi echo "CONFIG_TPM=y" >> $config_host_mak fi