All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration
@ 2011-08-31 14:35 Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
                   ` (14 more replies)
  0 siblings, 15 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

The following series of patches adds TPM (Trusted Platform Module) support
to Qemu. An emulator for the TIS (TPM Interface Spec) interface is
added that provides the basis for accessing a 'backend' implementing the actual
TPM functionality. The TIS emulator serves as a 'frontend' enabling for
example Linux's TPM TIS (tpm_tis) driver.

I am also posting the implementation of a backend implementation that is based
on a library (libtpms) providing TPM functionality. This library is currently
undergoing further testing but is now available via Fedora Rawhide.

For this patch series you have to use libtpms revision 9 available here:

x86_64:
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-0.5.1-9.x86_64.rpm
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-devel-0.5.1-9.x86_64.rpm

i686:
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-0.5.1-9.i686.rpm
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-devel-0.5.1-9.i686.rpm

Source rpm:

http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/source/SRPMS/libtpms-0.5.1-9.src.rpm

Once TPM support is check in to the Qemu git repository, I would like to
force the usage of libtpms-0.5.2.

Further, a backend 'null' driver is provided. This null driver responds to
every TPM request with a response indicating failure.

Testing was done primarily with the libtpms-based backend. It provides support
for VM suspend/resume, migration and snapshotting. It uses QCoW2 as the file
format for storing its persistent state onto, which is necessary for support
of snapshotting. Using Linux as the OS along with some recently posted patches
for the Linux TPM TIS driver, suspend/resume works fine (using 'virsh
save/restore') along with hibernation and OS suspend (ACPI S3).

Proper support for the TPM requires support in the BIOS since the BIOS
needs to initialize the TPM upon machine start or issue commands to the TPM
when it resumes from suspend (ACPI S3). It also builds and connects the
necessary ACPI tables (SSDT for TPM device, TCPA table for logging) to the
ones that are built by a BIOS. To support this I have a fairly extensive
set of extensions for SeaBIOS that have already been posted to the SeaBIOS
mailing list and been ACK'ed by Kevin (thank you! :-)).

v8:
 - applies to checkout of f0fb8b7 (Aug 30)
 - fixing compilation error pointed out by Andreas Niederl
 - adding patch that allows to feed an initial state into the libtpms TPM
 - following memory API changes (glib) where necessary

v7:
 - applies to checkout of b9c6cbf (Aug 9)
 - measuring the modules if multiboot is used
 - coding style fixes

v6:
 - applies to checkout of 75ef849 (July 2nd)
 - some fixes and improvements to existing patches; see individual patches
 - added a patch with a null driver responding to all TPM requests with
   a response indicating failure; this backend has no dependencies and
   can alwayy be built;
 - added a patch to support the hashing of kernel, ramfs and command line
   if those were passed to Qemu using -kernel, -initrd and -append
   respectively. Measurements are taken, logged, and passed to SeaBIOS using
   the firmware interface.
 - libtpms revision 7 now requires 83kb of block storage due to having more
   NVRAM space

v5:
 - applies to checkout of 1fddfba1
 - adding support for split command line using the -tpmdev ... -device ...
   options while keeping the -tpm option
 - support for querying the device models using -tpm model=?
 - support for monitor 'info tpm'
 - adding documentation of command line options for man page and web page
 - increasing room for ACPI tables that qemu reserves to 128kb (from 64kb)
 - adding (experimental) support for block migration
 - adding (experimental) support for taking measurements when kernel,
   initrd and kernel command line are directly passed to Qemu

v4:
 - applies to checkout of d2d979c6
 - more coding style fixes
 - adding patch for supporting blob encryption (in addition to the existing
   QCoW2-level encryption)
   - this allows for graceful termination of a migration if the target
     is detected to have a wrong key
   - tested with big and little endian hosts
 - main thread releases mutex while checking for work to do on behalf of
   backend
 - introducing file locking (fcntl) on the block layer for serializing access
   to shared (QCoW2) files (used during migration)

v3:
 - Building a null driver at patch 5/8 that responds to all requests
   with an error response; subsequently this driver is transformed to the
   libtpms-based driver for real TPM functionality
 - Reworked the threading; dropped the patch for qemu_thread_join; the
   main thread synchronizing with the TPM thread termination may need
   to write data to the block storage while waiting for the thread to 
   terminate; did not previously show a problem but is safer
 - A lot of testing based on recent git checkout 4b4a72e5 (4/10):
   - migration of i686 VM from x86_64 host to i686 host to ppc64 host while
     running tests inside the VM
   - tests with S3 suspend/resume
   - tests with snapshots
   - multiple-hour tests with VM suspend/resume (using virsh save/restore)
     while running a TPM test suite inside the VM
   All tests passed; [not all of them were done on the ppc64 host]

v2:
 - splitting some of the patches into smaller ones for easier review
 - fixes in individual patches

Regards,
    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-01 17:14   ` Michael S. Tsirkin
  2011-09-01 18:14   ` Michael S. Tsirkin
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
                   ` (13 subsequent siblings)
  14 siblings, 2 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm.diff --]
[-- Type: text/plain, Size: 21142 bytes --]

This patch adds support for TPM command line options.
The command line supported here (considering the libtpms based
backend) are

./qemu-... -tpm builtin,path=<path to blockstorage file>

and

./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
           -device tpm-tis,tpmdev=<id>

and

./qemu-... -tpmdev ?

where the latter works similar to -soundhw ? and shows a list of
available TPM backends ('builtin').

To show the available TPM models do:

./qemu-... -tpm model=?


In case of -tpm, 'type' (above 'builtin') and 'model' are interpreted in tpm.c.
In case of -tpmdev 'type' and 'id' are interpreted in tpm.c
Using the type parameter, the backend is chosen, i.e., 'builtin' for the
libtpms-based builtin TPM. The interpretation of the other parameters along
with determining whether enough parameters were provided is pushed into
the backend driver, which needs to implement the interface function
'create' and return a TPMDriver structure if the VM can be started or 'NULL'
if not enough or bad parameters were provided.

Since SeaBIOS will now use 128kb for ACPI tables the amount of reserved
memory for ACPI tables needs to be increased -- increasing it to 128kb.

Monitor support for 'info tpm' has been added. It for example prints the
following:

TPM devices:
  builtin: model=tpm-tis,id=tpm0

v8:
 - adjusting formatting of backend drivers output to accomodate better
   formatting of 'passthrough' backend output

v6:
 - use #idef CONFIG_TPM to surround TPM calls
 - use QLIST_FOREACH_SAFE rather than QLIST_FOREACH in tpm_cleanup
 - commented backend ops in tpm.h
 - moving to IRQ 5 (11 collided with network cards)

v5:
 - fixing typo reported by Serge Hallyn
 - Adapting code to split command line parameters supporting 
   -tpmdev ... -device tpm-tis,tpmdev=...
 - moved code out of arch_init.c|h into tpm.c|h
 - increasing reserved memory for ACPI tables to 128kb (from 64kb)
 - the backend interface has a create() function for interpreting the command
   line parameters and returning a TPMDevice structure; previoulsy
   this function was called handle_options()
 - the backend interface has a destroy() function for cleaning up after
   the create() function was called
 - added support for 'info tpm' in monitor

v4:
 - coding style fixes

v3:
 - added hw/tpm_tis.h to this patch so Qemu compiles at this stage

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

---
 Makefile.target |    1 
 hmp-commands.hx |    2 
 hw/pc.c         |    7 +
 hw/tpm_tis.h    |   75 +++++++++++++++
 monitor.c       |   10 ++
 qemu-config.c   |   46 +++++++++
 qemu-options.hx |   80 ++++++++++++++++
 tpm.c           |  279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tpm.h           |  112 ++++++++++++++++++++++
 vl.c            |   18 +++
 10 files changed, 629 insertions(+), 1 deletion(-)

Index: qemu-git/qemu-options.hx
===================================================================
--- qemu-git.orig/qemu-options.hx
+++ qemu-git/qemu-options.hx
@@ -1760,6 +1760,86 @@ ETEXI
 
 DEFHEADING()
 
+DEFHEADING(TPM device options:)
+
+#ifndef _WIN32
+# ifdef CONFIG_TPM
+DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
+    "" \
+    "-tpm builtin,path=<path>[,model=<model>]\n" \
+    "                enable a builtin TPM with state in file in path\n" \
+    "-tpm model=?    to list available TPM device models\n" \
+    "-tpm ?          to list available TPM backend types\n",
+    QEMU_ARCH_I386)
+DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
+    "-tpmdev [builtin],id=str[,option][,option][,...]\n",
+    QEMU_ARCH_I386)
+# endif
+#endif
+STEXI
+
+The general form of a TPM device option is:
+@table @option
+
+@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
+@findex -tpmdev
+Backend type must be:
+@option{builtin}.
+
+The specific backend type will determine the applicable options.
+The @code{-tpmdev} options requires a @code{-device} option.
+
+Options to each backend are described below.
+
+Use ? to print all available TPM backend types.
+@example
+qemu -tpmdev ?
+@end example
+
+@item -tpmdev builtin ,id=@var{id}, path=@var{path}
+
+Creates an instance of the built-in TPM.
+
+@option{path} specifies the path to the QCoW2 image that will store
+the TPM's persistent data. @option{path} is required.
+
+To create a built-in TPM use the following two options:
+@example
+-tpmdev builtin,id=tpm0,path=<path_to_qcow2> -device tpm-tis,tpmdev=tpm0
+@end example
+Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
+@code{tpmdev=tpm0} in the device option.
+
+@end table
+
+The short form of a TPM device option is:
+@table @option
+
+@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}]
+@findex -tpm
+
+@option{model} specifies the device model. The default device model is a
+@code{tpm-tis} device model. @code{model} is optional.
+
+Use ? to print all available TPM models.
+@example
+qemu -tpm model=?
+@end example
+
+The other options have the same meaning as explained above.
+
+To create a built-in TPM use the following option:
+@example
+-tpm builtin, path=<path_to_qcow2>
+@end example
+
+@end table
+
+ETEXI
+
+
+DEFHEADING()
+
 DEFHEADING(Linux/Multiboot boot specific:)
 STEXI
 
Index: qemu-git/vl.c
===================================================================
--- qemu-git.orig/vl.c
+++ qemu-git/vl.c
@@ -137,6 +137,7 @@ int main(int argc, char **argv)
 #include "block.h"
 #include "blockdev.h"
 #include "block-migration.h"
+#include "tpm.h"
 #include "dma.h"
 #include "audio/audio.h"
 #include "migration.h"
@@ -2498,6 +2499,14 @@ int main(int argc, char **argv, char **e
                 ram_size = value;
                 break;
             }
+#ifdef CONFIG_TPM
+            case QEMU_OPTION_tpm:
+                tpm_config_parse(qemu_find_opts("tpm"), optarg);
+                break;
+            case QEMU_OPTION_tpmdev:
+                tpm_config_parse(qemu_find_opts("tpmdev"), optarg);
+                break;
+#endif
             case QEMU_OPTION_mempath:
                 mem_path = optarg;
                 break;
@@ -3149,6 +3158,12 @@ int main(int argc, char **argv, char **e
         exit(1);
     }
 
+#ifdef CONFIG_TPM
+    if (tpm_init() < 0) {
+        exit(1);
+    }
+#endif
+
     /* init the bluetooth world */
     if (foreach_device_config(DEV_BT, bt_parse))
         exit(1);
@@ -3394,6 +3409,9 @@ int main(int argc, char **argv, char **e
     quit_timers();
     net_cleanup();
     res_free();
+#ifdef CONFIG_TPM
+    tpm_cleanup();
+#endif
 
     return 0;
 }
Index: qemu-git/qemu-config.c
===================================================================
--- qemu-git.orig/qemu-config.c
+++ qemu-git/qemu-config.c
@@ -507,6 +507,50 @@ QemuOptsList qemu_boot_opts = {
     },
 };
 
+static QemuOptsList qemu_tpmdev_opts = {
+    .name = "tpmdev",
+    .implied_opt_name = "type",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head),
+    .desc = {
+        {
+            .name = "type",
+            .type = QEMU_OPT_STRING,
+            .help = "Type of TPM backend",
+        },
+        {
+            .name = "path",
+            .type = QEMU_OPT_STRING,
+            .help = "Persistent storage for TPM state",
+        },
+        { /* end of list */ }
+    },
+};
+
+static QemuOptsList qemu_tpm_opts = {
+    .name = "tpm",
+    .implied_opt_name = "type",
+    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpm_opts.head),
+    .desc = {
+        {
+            .name = "type",
+            .type = QEMU_OPT_STRING,
+            .help = "Type of TPM backend",
+        },
+        {
+            .name = "model",
+            .type = QEMU_OPT_STRING,
+            .help = "Model of TPM frontend",
+        },
+        {
+            .name = "path",
+            .type = QEMU_OPT_STRING,
+            .help = "Persistent storage for TPM state",
+        },
+        { /* end of list */ }
+    },
+};
+
+
 static QemuOptsList *vm_config_groups[32] = {
     &qemu_drive_opts,
     &qemu_chardev_opts,
@@ -523,6 +567,8 @@ static QemuOptsList *vm_config_groups[32
     &qemu_option_rom_opts,
     &qemu_machine_opts,
     &qemu_boot_opts,
+    &qemu_tpmdev_opts,
+    &qemu_tpm_opts,
     NULL,
 };
 
Index: qemu-git/hw/tpm_tis.h
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.h
@@ -0,0 +1,75 @@
+/*
+ * tpm_tis.h - include file for tpm_tis.c
+ *
+ * Copyright (C) 2006,2010,2011 IBM Corporation
+ *
+ * Author: Stefan Berger <stefanb@us.ibm.com>
+ *         David Safford <safford@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+#ifndef _HW_TPM_TIS_H
+#define _HW_TPM_TIS_H
+
+#include "isa.h"
+#include "block_int.h"
+#include "qemu-thread.h"
+
+#include <stdint.h>
+
+#define TIS_ADDR_BASE       0xFED40000
+
+#define NUM_LOCALITIES      5     /* per spec */
+#define NO_LOCALITY         0xff
+
+#define IS_VALID_LOCTY(x)   ((x) < NUM_LOCALITIES)
+
+
+#define TPM_TIS_IRQ         5
+
+#define TIS_TPM_BUFFER_MAX  4096
+
+
+typedef struct TPMSizedBuffer {
+    uint32_t size;
+    uint8_t  *buffer;
+} TPMSizedBuffer;
+
+
+enum tis_state {
+    STATE_IDLE = 0,
+    STATE_READY,
+    STATE_COMPLETION,
+    STATE_EXECUTION,
+    STATE_RECEPTION,
+};
+
+
+void tis_reset_for_snapshot_resume(TPMState *s);
+
+
+/* utility functions */
+
+static inline uint16_t tis_get_size_from_buffer(const TPMSizedBuffer *sb)
+{
+    return (sb->buffer[4] << 8) + sb->buffer[5];
+}
+
+static inline void dumpBuffer(FILE *stream,
+                              unsigned char *buffer, unsigned int len)
+{
+    int i;
+
+    for (i = 0; i < len; i++) {
+        if (i && !(i % 16)) {
+            fprintf(stream, "\n");
+        }
+        fprintf(stream, "%.2X ", buffer[i]);
+    }
+    fprintf(stream, "\n");
+}
+
+#endif /* _HW_TPM_TIS_H */
Index: qemu-git/tpm.c
===================================================================
--- /dev/null
+++ qemu-git/tpm.c
@@ -0,0 +1,279 @@
+/*
+ * TPM configuraion
+ *
+ * Copyright (C) 2011 IBM Corporation
+ * Copyright (C) 2011 Stefan Berger
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ * Based on net.c
+ */
+#include "config.h"
+
+#include "tpm.h"
+#include "monitor.h"
+#include "qerror.h"
+
+
+#ifdef CONFIG_TPM
+
+#if defined(TARGET_I386) || defined(TARGET_X86_64)
+
+static const TPMDriverOps *bes[] = {
+    NULL,
+};
+
+
+static const char *tpm_models[] = {
+    TPM_DEFAULT_DEVICE_MODEL,
+    NULL,
+};
+
+
+static QLIST_HEAD(, TPMBackend) tpm_backends =
+    QLIST_HEAD_INITIALIZER(tpm_backends);
+
+
+const TPMDriverOps *tpm_get_backend_driver(const char *id)
+{
+    int i;
+
+    for (i = 0; bes[i] != NULL; i++) {
+        if (!strcmp(bes[i]->id, id)) {
+            break;
+        }
+    }
+
+    return bes[i];
+}
+
+
+static void tpm_display_models(FILE *out)
+{
+    int i;
+
+    fprintf(stderr, "qemu: Supported TPM models: ");
+    for (i = 0 ; tpm_models[i]; i++) {
+        fprintf(stderr, "%s%c", tpm_models[i], tpm_models[i+1] ? ',' : '\n');
+    }
+}
+
+
+static int tpm_check_model(const char *model)
+{
+    int i;
+
+    for (i = 0 ; tpm_models[i]; i++) {
+        if (strcmp(tpm_models[i], model) == 0) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+void tpm_display_backend_drivers(FILE *out)
+{
+    int i;
+
+    fprintf(out, "Supported TPM types (choose only one):\n");
+
+    for (i = 0; bes[i] != NULL; i++) {
+        fprintf(out, "%12s   %s",
+                bes[i]->id, bes[i]->desc());
+        fprintf(out, "\n");
+    }
+    fprintf(out, "\n");
+}
+
+
+TPMBackend *qemu_find_tpm(const char *id)
+{
+    TPMBackend *drv;
+
+    QLIST_FOREACH(drv, &tpm_backends, list) {
+        if (!strcmp(drv->id, id)) {
+            return drv;
+        }
+    }
+
+    return NULL;
+}
+
+
+void do_info_tpm(Monitor *mon)
+{
+    TPMBackend *drv;
+    const char *model;
+
+    monitor_printf(mon, "TPM devices:\n");
+
+    QLIST_FOREACH(drv, &tpm_backends, list) {
+        model = drv->model ? drv->model : TPM_DEFAULT_DEVICE_MODEL;
+        monitor_printf(mon, "  %s: model=%s,id=%s\n",
+                       drv->ops->id, model, drv->id);
+    }
+}
+
+/*
+ * Create those TPMs that were created with -tpm rather than -tpmdev.
+ * The ones created with -tpm have a 'model' name.
+ */
+void qemu_create_tpm(void)
+{
+    TPMBackend *drv;
+
+    QLIST_FOREACH(drv, &tpm_backends, list) {
+        if (drv->model) {
+            if (strcmp(drv->model, TPM_DEFAULT_DEVICE_MODEL) == 0) {
+                isa_create_simple(drv->model);
+            }
+        }
+    }
+}
+
+
+static int configure_tpm(QemuOpts *opts, int is_tpmdev)
+{
+    const char *value;
+    const char *id = TPM_DEFAULT_DEVICE_ID;
+    const char *model =  NULL;
+    const TPMDriverOps *be;
+    TPMBackend *drv;
+
+    if (!QLIST_EMPTY(&tpm_backends)) {
+        fprintf(stderr, "Only one TPM is allowed.\n");
+        return 1;
+    }
+
+    if (is_tpmdev) {
+        id = qemu_opts_id(opts);
+        if (id == NULL) {
+            qerror_report(QERR_MISSING_PARAMETER, "id");
+            return 1;
+        }
+    } else {
+        model = qemu_opt_get(opts, "model");
+        if (model) {
+            if (strcmp(model, "?") == 0) {
+                tpm_display_models(stdout);
+                return 1;
+            }
+            if (!tpm_check_model(model)) {
+                qerror_report(QERR_INVALID_PARAMETER_VALUE, "model",
+                              "a tpm model");
+                tpm_display_models(stderr);
+                return 1;
+            }
+        } else {
+            model = TPM_DEFAULT_DEVICE_MODEL;
+        }
+    }
+
+    value = qemu_opt_get(opts, "type");
+    if (!value) {
+        qerror_report(QERR_MISSING_PARAMETER, "type");
+        tpm_display_backend_drivers(stderr);
+        return 1;
+    }
+
+    be = tpm_get_backend_driver(value);
+    if (be == NULL) {
+        qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
+                      "a tpm backend type");
+        tpm_display_backend_drivers(stderr);
+        return 1;
+    }
+
+    assert((is_tpmdev && model == NULL) || (!is_tpmdev && model != NULL));
+
+    drv = be->create(opts, id, model);
+    if (!drv) {
+        return 1;
+    }
+
+    QLIST_INSERT_HEAD(&tpm_backends, drv, list);
+
+    return 0;
+}
+
+
+static int tpm_init_tpmdev(QemuOpts *opts, void *dummy)
+{
+    return configure_tpm(opts, 1);
+}
+
+
+static int tpm_init_tpm(QemuOpts *opts, void *dummy)
+{
+    return configure_tpm(opts, 0);
+}
+
+
+int tpm_init(void)
+{
+    if (qemu_opts_foreach(qemu_find_opts("tpmdev"),
+                          tpm_init_tpmdev, NULL, 1) != 0) {
+        return -1;
+    }
+
+    if (qemu_opts_foreach(qemu_find_opts("tpm"),
+                          tpm_init_tpm, NULL, 1) != 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+
+void tpm_cleanup(void)
+{
+    TPMBackend *drv, *next;
+
+    QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) {
+        QLIST_REMOVE(drv, list);
+        drv->ops->destroy(drv);
+    }
+}
+
+
+void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
+{
+    QemuOpts *opts;
+
+    if (strcmp("none", optarg) != 0) {
+        if (*optarg == '?') {
+            tpm_display_backend_drivers(stdout);
+            exit(0);
+        }
+        opts = qemu_opts_parse(opts_list, optarg, 1);
+        if (!opts) {
+            exit(1);
+        }
+    }
+}
+
+# else /* TARGET_I386 || TARGET_X86_64 */
+
+void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
+{
+}
+
+int tpm_init(void)
+{
+    return 0;
+}
+
+void tpm_cleanup(void)
+{
+}
+
+void do_info_tpm(Monitor *mon)
+{
+    monitor_printf(mon, "TPM support: not compiled\n");
+}
+
+# endif
+#endif /* CONFIG_TPM */
Index: qemu-git/tpm.h
===================================================================
--- /dev/null
+++ qemu-git/tpm.h
@@ -0,0 +1,112 @@
+#ifndef _HW_TPM_CONFIG_H
+#define _HW_TPM_CONFIG_H
+
+struct TPMState;
+typedef struct TPMState TPMState;
+
+#include "hw/tpm_tis.h"
+
+struct TPMDriverOps;
+typedef struct TPMDriverOps TPMDriverOps;
+
+typedef struct TPMBackend {
+    char *id;
+    char *model;
+    TPMDriverOps *ops;
+
+    QLIST_ENTRY(TPMBackend) list;
+} TPMBackend;
+
+
+/* locality data  -- all fields are persisted */
+typedef struct TPMLocality {
+    enum tis_state state;
+    uint8_t access;
+    uint8_t sts;
+    uint32_t inte;
+    uint32_t ints;
+
+    uint16_t w_offset;
+    uint16_t r_offset;
+    TPMSizedBuffer w_buffer;
+    TPMSizedBuffer r_buffer;
+} TPMLocality;
+
+
+/* overall state of the TPM interface */
+struct TPMState {
+    ISADevice busdev;
+
+    uint32_t offset;
+    uint8_t buf[TIS_TPM_BUFFER_MAX];
+
+    uint8_t active_locty;
+    uint8_t aborting_locty;
+    uint8_t next_locty;
+
+    uint8_t command_locty;
+    TPMLocality loc[NUM_LOCALITIES];
+
+    qemu_irq irq;
+    uint32_t irq_num;
+
+    QemuMutex state_lock;
+    QemuCond  from_tpm_cond;
+    QemuCond  to_tpm_cond;
+    bool      to_tpm_execute;
+
+    bool      tpm_initialized;
+
+    char *backend;
+    TPMBackend *be_driver;
+};
+
+
+typedef void (TPMRecvDataCB)(TPMState *s, uint8_t locty);
+
+struct TPMDriverOps {
+    const char *id;
+    /* get a descriptive text of the backend to display to the user */
+    const char *(*desc)(void);
+
+    void (*job_for_main_thread)(void *);
+
+    TPMBackend *(*create)(QemuOpts *, const char *id, const char *model);
+    void (*destroy)(TPMBackend *drv);
+
+    /* initialize the backend */
+    int (*init)(TPMState *s, TPMRecvDataCB *datacb);
+    /* start up the TPM on the backend early if possible */
+    int (*early_startup_tpm)(void);
+    /* start up the TPM on the backend late if necessary */
+    int (*late_startup_tpm)(void);
+    /* returns true if nothing will ever answer TPM requests */
+    bool (*had_startup_error)(void);
+
+    size_t (*realloc_buffer)(TPMSizedBuffer *sb);
+
+    void (*reset)(void);
+
+    /* called to trigger the saving of the volatile data;
+       called before the VM suspends / migrates */
+    int (*save_volatile_data)(void);
+    /* triggers the loading of the volatile data */
+    int (*load_volatile_data)(TPMState *s);
+
+    bool (*get_tpm_established_flag)(void);
+};
+
+#define TPM_DEFAULT_DEVICE_ID    "tpm0"
+#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
+
+void tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
+int tpm_init(void);
+void tpm_cleanup(void);
+void qemu_create_tpm(void);
+TPMBackend *qemu_find_tpm(const char *id);
+void do_info_tpm(Monitor *mon);
+void tpm_display_backend_drivers(FILE *out);
+const TPMDriverOps *tpm_get_backend_driver(const char *id);
+
+
+#endif /* _HW_TPM_CONFIG_H */
Index: qemu-git/hw/pc.c
===================================================================
--- qemu-git.orig/hw/pc.c
+++ qemu-git/hw/pc.c
@@ -43,6 +43,7 @@
 #include "ui/qemu-spice.h"
 #include "memory.h"
 #include "exec-memory.h"
+#include "tpm.h"
 
 /* output Bochs bios info messages */
 //#define DEBUG_BIOS
@@ -62,7 +63,7 @@
 #define PC_MAX_BIOS_SIZE (4 * 1024 * 1024)
 
 /* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables.  */
-#define ACPI_DATA_SIZE       0x10000
+#define ACPI_DATA_SIZE       0x20000
 #define BIOS_CFG_IOPORT 0x510
 #define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0)
 #define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1)
@@ -1183,6 +1184,10 @@ void pc_basic_device_init(qemu_irq *isa_
         fd[i] = drive_get(IF_FLOPPY, 0, i);
     }
     fdctrl_init_isa(fd);
+
+#ifdef CONFIG_TPM
+    qemu_create_tpm();
+#endif
 }
 
 void pc_pci_device_init(PCIBus *pci_bus)
Index: qemu-git/monitor.c
===================================================================
--- qemu-git.orig/monitor.c
+++ qemu-git/monitor.c
@@ -47,6 +47,7 @@
 #include "migration.h"
 #include "kvm.h"
 #include "acl.h"
+#include "tpm.h"
 #include "qint.h"
 #include "qfloat.h"
 #include "qlist.h"
@@ -3151,6 +3152,15 @@ static const mon_cmd_t info_cmds[] = {
         .mhandler.info = do_info_trace_events,
     },
 #endif
+#if defined(CONFIG_TPM)
+    {
+        .name       = "tpm",
+        .args_type  = "",
+        .params     = "",
+        .help       = "show the TPM devices",
+        .mhandler.info = do_info_tpm,
+    },
+#endif
     {
         .name       = NULL,
     },
Index: qemu-git/hmp-commands.hx
===================================================================
--- qemu-git.orig/hmp-commands.hx
+++ qemu-git/hmp-commands.hx
@@ -1351,6 +1351,8 @@ show device tree
 show qdev device model list
 @item info roms
 show roms
+@item info tpm
+show the TPM devices
 @end table
 ETEXI
 
Index: qemu-git/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -200,6 +200,7 @@ obj-$(CONFIG_KVM) += kvm.o kvm-all.o
 obj-$(CONFIG_NO_KVM) += kvm-stub.o
 obj-y += memory.o
 LIBS+=-lz
+obj-y += tpm.o
 
 QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
 QEMU_CFLAGS += $(VNC_SASL_CFLAGS)

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-09 19:28   ` Paul Moore
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver Stefan Berger
                   ` (12 subsequent siblings)
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_tis.diff --]
[-- Type: text/plain, Size: 27687 bytes --]

This patch adds the main code of the TPM frontend driver, the TPM TIS
interface, to Qemu. The code is largely based on the previous implementation
for Xen but has been significantly extended to meet the standard's
requirements, such as the support for changing of localities and all the
functionality of the available flags.

Communication with the backend (i.e., for Xen or the libtpms-based one)
is cleanly separated through an interface which the backend driver needs
to implement.

The TPM TIS driver's backend was previously chosen in the code added
to arch_init. The frontend holds a pointer to the chosen backend (interface).

Communication with the backend is largely based on signals and conditions.
Whenever the frontend has collected a complete packet, it will signal
the backend, which then starts processing the command. Once the result
has been returned, the backend invokes a callback function
(tis_tpm_receive_cb()).

The one tricky part is support for VM suspend while the TPM is processing
a command. In this case the frontend driver is waiting for the backend
to return the result of the last command before shutting down. It waits
on a condition for a signal from the backend, which is delivered in 
tis_tpm_receive_cb().

Testing the proper functioning of the different flags and localities 
cannot be done from user space when running in Linux for example, since
access to the address space of the TPM TIS interface is not possible. Also
the Linux driver itself does not exercise all functionality. So, for
testing there is a fairly extensive test suite as part of the SeaBIOS patches
since from within the BIOS one can have full access to all the TPM's registers.

v5:
  - adding comment to tis_data_read
  - refactoring following support for split command line options
    -tpmdev and -device
  - code handling the configuration of the TPM device was moved to tpm.c
  - removed empty line at end of file

v3:
  - prefixing functions with tis_
  - added a function to the backend interface 'early_startup_tpm' that
    allows to detect the presence of the block storage and gracefully fails
    Qemu if it's not available. This works with migration using shared
    storage but doesn't support migration with block storage migration.
    For encyrypted QCoW2 and in case of a snapshot resue the late_startup_tpm
    interface function is called

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

---
 hw/tpm_tis.c |  841 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 841 insertions(+)

Index: qemu-git/hw/tpm_tis.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.c
@@ -0,0 +1,841 @@
+/*
+ * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
+ *
+ * Copyright (C) 2006,2010 IBM Corporation
+ *
+ * Author: Stefan Berger <stefanb@us.ibm.com>
+ *         David Safford <safford@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ *
+ * Implementation of the TIS interface according to specs at
+ * https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpecification_1-20_1-00_FINAL.pdf
+ *
+ */
+
+#include "tpm.h"
+#include "block.h"
+#include "hw/hw.h"
+#include "hw/pc.h"
+#include "hw/tpm_tis.h"
+
+#include <stdio.h>
+
+//#define DEBUG_TIS
+
+/* whether the STS interrupt is supported */
+//#define RAISE_STS_IRQ
+
+/* tis registers */
+#define TIS_REG_ACCESS                0x00
+#define TIS_REG_INT_ENABLE            0x08
+#define TIS_REG_INT_VECTOR            0x0c
+#define TIS_REG_INT_STATUS            0x10
+#define TIS_REG_INTF_CAPABILITY       0x14
+#define TIS_REG_STS                   0x18
+#define TIS_REG_DATA_FIFO             0x24
+#define TIS_REG_DID_VID               0xf00
+#define TIS_REG_RID                   0xf04
+
+
+#define STS_VALID                    (1 << 7)
+#define STS_COMMAND_READY            (1 << 6)
+#define STS_TPM_GO                   (1 << 5)
+#define STS_DATA_AVAILABLE           (1 << 4)
+#define STS_EXPECT                   (1 << 3)
+#define STS_RESPONSE_RETRY           (1 << 1)
+
+#define ACCESS_TPM_REG_VALID_STS     (1 << 7)
+#define ACCESS_ACTIVE_LOCALITY       (1 << 5)
+#define ACCESS_BEEN_SEIZED           (1 << 4)
+#define ACCESS_SEIZE                 (1 << 3)
+#define ACCESS_PENDING_REQUEST       (1 << 2)
+#define ACCESS_REQUEST_USE           (1 << 1)
+#define ACCESS_TPM_ESTABLISHMENT     (1 << 0)
+
+#define INT_ENABLED                  (1 << 31)
+#define INT_DATA_AVAILABLE           (1 << 0)
+#define INT_STS_VALID                (1 << 1)
+#define INT_LOCALITY_CHANGED         (1 << 2)
+#define INT_COMMAND_READY            (1 << 7)
+
+#ifndef RAISE_STS_IRQ
+
+# define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
+                                       INT_DATA_AVAILABLE   | \
+                                       INT_COMMAND_READY)
+
+#else
+
+# define INTERRUPTS_SUPPORTED         (INT_LOCALITY_CHANGED | \
+                                       INT_DATA_AVAILABLE   | \
+                                       INT_STS_VALID | \
+                                       INT_COMMAND_READY)
+
+#endif
+
+#define CAPABILITIES_SUPPORTED       ((1 << 4) |            \
+                                      INTERRUPTS_SUPPORTED)
+
+#define TPM_DID          0x0001
+#define TPM_VID          0x0001
+#define TPM_RID          0x0001
+
+#define TPM_NO_DATA_BYTE 0xff
+
+/* prototypes */
+static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr);
+
+
+#ifdef DEBUG_TIS
+static void tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
+{
+    uint16_t len;
+
+    len = tis_get_size_from_buffer(sb);
+    fprintf(stderr, "tpm_tis: %s length = %d\n", string, len);
+    dumpBuffer(stderr, sb->buffer, len);
+}
+#endif
+
+
+static inline uint8_t tis_locality_from_addr(target_phys_addr_t addr)
+{
+    return (uint8_t)((addr >> 12) & 0x7);
+}
+
+
+/*
+ * Send a TPM request.
+ * Call this with the state_lock held so we can sync with the receive
+ * callback.
+ */
+static void tis_tpm_send(TPMState *s, uint8_t locty)
+{
+#ifdef DEBUG_TIS
+    tis_show_buffer(&s->loc[locty].w_buffer, "tpm_tis: To TPM");
+#endif
+    s->command_locty = locty;
+
+    /* w_offset serves as length indicator for length of data;
+       it's reset when the response comes back */
+    s->loc[locty].state = STATE_EXECUTION;
+    s->loc[locty].sts &= ~STS_EXPECT;
+
+    s->to_tpm_execute = true;
+    qemu_cond_signal(&s->to_tpm_cond);
+}
+
+
+/* raise an interrupt if allowed */
+static void tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
+{
+    if (!IS_VALID_LOCTY(locty)) {
+        return;
+    }
+
+    if ((s->loc[locty].inte & INT_ENABLED) &&
+        (s->loc[locty].inte & irqmask)) {
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Raising IRQ for flag %08x\n", irqmask);
+#endif
+        qemu_irq_raise(s->irq);
+        s->loc[locty].ints |= irqmask;
+    }
+}
+
+
+static uint32_t tis_check_request_use_except(TPMState *s, uint8_t locty)
+{
+    uint8_t l;
+
+    for (l = 0; l < NUM_LOCALITIES; l++) {
+        if (l == locty) {
+            continue;
+        }
+        if ((s->loc[l].access & ACCESS_REQUEST_USE)) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+
+static void tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
+{
+    int change = (s->active_locty != new_active_locty);
+
+    if (change && IS_VALID_LOCTY(s->active_locty)) {
+        /* reset flags on the old active locality */
+        s->loc[s->active_locty].access &= ~(ACCESS_ACTIVE_LOCALITY|
+                                            ACCESS_REQUEST_USE);
+        if (IS_VALID_LOCTY(new_active_locty) &&
+            s->loc[new_active_locty].access & ACCESS_SEIZE) {
+            s->loc[s->active_locty].access |= ACCESS_BEEN_SEIZED;
+        }
+    }
+
+    s->active_locty = new_active_locty;
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: Active locality is now %d\n", s->active_locty);
+#endif
+
+    if (IS_VALID_LOCTY(new_active_locty)) {
+        /* set flags on the new active locality */
+        s->loc[new_active_locty].access |= ACCESS_ACTIVE_LOCALITY;
+        s->loc[new_active_locty].access &= ~(ACCESS_REQUEST_USE |
+                                             ACCESS_SEIZE);
+    }
+
+    if (change) {
+        tis_raise_irq(s, s->active_locty, INT_LOCALITY_CHANGED);
+    }
+}
+
+
+/* abort -- this function switches the locality */
+static void tis_abort(TPMState *s, uint8_t locty)
+{
+    s->loc[locty].r_offset = 0;
+    s->loc[locty].w_offset = 0;
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: tis_abort: new active locality is %d\n",
+            s->next_locty);
+#endif
+
+    /*
+     * Need to react differently depending on who's aborting now and
+     * which locality will become active afterwards.
+     */
+    if (s->aborting_locty == s->next_locty) {
+        s->loc[s->aborting_locty].state = STATE_READY;
+        s->loc[s->aborting_locty].sts   = STS_COMMAND_READY;
+        tis_raise_irq(s, s->aborting_locty, INT_COMMAND_READY);
+    }
+
+    /* locality after abort is another one than the current one */
+    tis_new_active_locality(s, s->next_locty);
+
+    s->next_locty = NO_LOCALITY;
+    s->aborting_locty = NO_LOCALITY; /* nobody's aborting a command anymore */
+}
+
+
+/* prepare aborting current command */
+static void tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
+{
+    uint8_t busy_locty;
+
+    s->aborting_locty = locty;
+    s->next_locty = newlocty;  /* locality after successful abort */
+
+    /*
+     * only abort a command using an interrupt if currently executing
+     * a command AND if there's a valid connection to the vTPM.
+     */
+    for (busy_locty = 0; busy_locty < NUM_LOCALITIES; busy_locty++) {
+        if (s->loc[busy_locty].state == STATE_EXECUTION) {
+            /* there is currently no way to interrupt the TPM's operations
+               while it's executing a command; once the TPM is done and
+               returns the buffer, it will switch to the next_locty; */
+#ifdef DEBUG_TIS
+            fprintf(stderr, "tpm_tis: Locality %d is busy - "
+                            "deferring abort\n", busy_locty);
+#endif
+            return;
+        }
+    }
+
+    tis_abort(s, locty);
+}
+
+
+/*
+ * Callback from the TPM to indicate that the response was received.
+ */
+static void tis_tpm_receive_cb(TPMState *s, uint8_t locty)
+{
+    qemu_mutex_lock(&s->state_lock);
+
+    s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+    s->loc[locty].state = STATE_COMPLETION;
+    s->loc[locty].r_offset = 0;
+    s->loc[locty].w_offset = 0;
+
+    if (IS_VALID_LOCTY(s->next_locty)) {
+        tis_abort(s, locty);
+    }
+
+    qemu_cond_signal(&s->from_tpm_cond);
+
+    qemu_mutex_unlock(&s->state_lock);
+
+#ifndef RAISE_STS_IRQ
+    tis_raise_irq(s, locty, INT_DATA_AVAILABLE);
+#else
+    tis_raise_irq(s, locty, INT_DATA_AVAILABLE | INT_STS_VALID);
+#endif
+}
+
+
+/*
+ * read a byte of response data
+ * call this with s->state_lock held
+ */
+static uint32_t tis_data_read(TPMState *s, uint8_t locty)
+{
+    uint32_t ret = TPM_NO_DATA_BYTE;
+    uint16_t len;
+
+    if ((s->loc[locty].sts & STS_DATA_AVAILABLE)) {
+        len = tis_get_size_from_buffer(&s->loc[locty].r_buffer);
+
+        ret = s->loc[locty].r_buffer.buffer[s->loc[locty].r_offset++];
+        if (s->loc[locty].r_offset >= len) {
+            /* got last byte */
+            s->loc[locty].sts = STS_VALID;
+#ifdef RAISE_STS_IRQ
+            tis_raise_irq(s, locty, INT_STS_VALID);
+#endif
+        }
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: tis_data_read byte 0x%02x   [%d]\n",
+                ret, s->loc[locty].r_offset-1);
+#endif
+    }
+
+    return ret;
+}
+
+
+/*
+ * Read a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static uint32_t tis_mem_readl(void *opaque, target_phys_addr_t addr)
+{
+    TPMState *s = opaque;
+    uint16_t offset = addr & 0xffc;
+    uint8_t shift = (addr & 0x3) * 8;
+    uint32_t val = 0xff;
+    uint8_t locty = tis_locality_from_addr(addr);
+
+    qemu_mutex_lock(&s->state_lock);
+
+    if (!s->tpm_initialized) {
+        s->be_driver->ops->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (s->be_driver->ops->had_startup_error()) {
+        qemu_mutex_unlock(&s->state_lock);
+        return 0xFFFFFFFF;
+    }
+
+    switch (offset) {
+    case TIS_REG_ACCESS:
+        /* never show the SEIZE flag even though we use it internally */
+        val = s->loc[locty].access & ~ACCESS_SEIZE;
+        /* the pending flag is alawys calculated */
+        if (tis_check_request_use_except(s, locty)) {
+            val |= ACCESS_PENDING_REQUEST;
+        }
+        val |= !s->be_driver->ops->get_tpm_established_flag();
+        break;
+    case TIS_REG_INT_ENABLE:
+        val = s->loc[locty].inte;
+        break;
+    case TIS_REG_INT_VECTOR:
+        val = s->irq_num;
+        break;
+    case TIS_REG_INT_STATUS:
+        val = s->loc[locty].ints;
+        break;
+    case TIS_REG_INTF_CAPABILITY:
+        val = CAPABILITIES_SUPPORTED;
+        break;
+    case TIS_REG_STS:
+        if (s->active_locty == locty) {
+            if ((s->loc[locty].sts & STS_DATA_AVAILABLE)) {
+                val =  (tis_get_size_from_buffer(&s->loc[locty].r_buffer) -
+                        s->loc[locty].r_offset) << 8 | s->loc[locty].sts;
+            } else {
+                val = (s->loc[locty].w_buffer.size -
+                       s->loc[locty].w_offset) << 8 | s->loc[locty].sts;
+            }
+        }
+        break;
+    case TIS_REG_DATA_FIFO:
+        if (s->active_locty == locty) {
+            switch (s->loc[locty].state) {
+            case STATE_COMPLETION:
+                val = tis_data_read(s, locty);
+                break;
+            default:
+                val = TPM_NO_DATA_BYTE;
+                break;
+            }
+        }
+        break;
+    case TIS_REG_DID_VID:
+        val = (TPM_DID << 16) | TPM_VID;
+        break;
+    case TIS_REG_RID:
+        val = TPM_RID;
+        break;
+    }
+
+    qemu_mutex_unlock(&s->state_lock);
+
+    if (shift) {
+        val >>= shift;
+    }
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis:  read(%08x) = %08x\n", (int)addr, val);
+#endif
+
+    return val;
+}
+
+
+/*
+ * Write a value to a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static void tis_mem_writel_intern(void *opaque, target_phys_addr_t addr,
+                                  uint32_t val, bool hw_access)
+{
+    TPMState *s = opaque;
+    uint16_t off = addr & 0xfff;
+    uint8_t locty = tis_locality_from_addr(addr);
+    uint8_t active_locty, l;
+    int c, set_new_locty = 1;
+    uint16_t len;
+
+#ifdef DEBUG_TIS
+    fprintf(stderr, "tpm_tis: write(%08x) = %08x\n", (int)addr, val);
+#endif
+
+    qemu_mutex_lock(&s->state_lock);
+
+    if (!s->tpm_initialized) {
+        s->be_driver->ops->late_startup_tpm();
+        s->tpm_initialized = true;
+    }
+
+    if (s->be_driver->ops->had_startup_error()) {
+        qemu_mutex_unlock(&s->state_lock);
+        return;
+    }
+
+    switch (off) {
+    case TIS_REG_ACCESS:
+
+        if ((val & ACCESS_SEIZE)) {
+            val &= ~(ACCESS_REQUEST_USE | ACCESS_ACTIVE_LOCALITY);
+        }
+
+        active_locty = s->active_locty;
+
+        if ((val & ACCESS_ACTIVE_LOCALITY)) {
+            /* give up locality if currently owned */
+            if (s->active_locty == locty) {
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: Releasing locality %d\n", locty);
+#endif
+                uint8_t newlocty = NO_LOCALITY;
+                /* anybody wants the locality ? */
+                for (c = NUM_LOCALITIES - 1; c >= 0; c--) {
+                    if ((s->loc[c].access & ACCESS_REQUEST_USE)) {
+#ifdef DEBUG_TIS
+                        fprintf(stderr, "tpm_tis: Locality %d requests use.\n",
+                                c);
+#endif
+                        newlocty = c;
+                        break;
+                    }
+                }
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: ACCESS_ACTIVE_LOCALITY: "
+                                "Next active locality: %d\n",
+                                newlocty);
+#endif
+                if (IS_VALID_LOCTY(newlocty)) {
+                    set_new_locty = 0;
+                    tis_prep_abort(s, locty, newlocty);
+                } else {
+                    active_locty = NO_LOCALITY;
+                }
+            } else {
+                /* not currently the owner; clear a pending request */
+                s->loc[locty].access &= ~ACCESS_REQUEST_USE;
+            }
+        }
+
+        if ((val & ACCESS_BEEN_SEIZED)) {
+            s->loc[locty].access &= ~ACCESS_BEEN_SEIZED;
+        }
+
+        if ((val & ACCESS_SEIZE)) {
+            /* allow seize if a locality is active and the requesting
+               locality is higher than the one that's active
+               OR
+               allow seize for requesting locality if no locality is
+               active */
+            while ((IS_VALID_LOCTY(s->active_locty) &&
+                    locty > s->active_locty) ||
+                   (!IS_VALID_LOCTY(s->active_locty))) {
+
+                /* already a pending SEIZE ? */
+                if ((s->loc[locty].access & ACCESS_SEIZE)) {
+                    break;
+                }
+
+                /* check for ongoing seize by a higher locality */
+                for (l = locty + 1; l < NUM_LOCALITIES; l++) {
+                    if ((s->loc[l].access & ACCESS_SEIZE)) {
+                        break;
+                    }
+                }
+
+                /* cancel any seize by a lower locality */
+                for (l = 0; l < locty - 1; l++) {
+                    s->loc[l].access &= ~ACCESS_SEIZE;
+                }
+
+                s->loc[locty].access |= ACCESS_SEIZE;
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: ACCESS_SEIZE: "
+                                "Locality %d seized from locality %d\n",
+                                locty, s->active_locty);
+                fprintf(stderr, "tpm_tis: ACCESS_SEIZE: Initiating abort.\n");
+#endif
+                set_new_locty = 0;
+                tis_prep_abort(s, s->active_locty, locty);
+                break;
+            }
+        }
+
+        if ((val & ACCESS_REQUEST_USE)) {
+            if (s->active_locty != locty) {
+                if (IS_VALID_LOCTY(s->active_locty)) {
+                    s->loc[locty].access |= ACCESS_REQUEST_USE;
+                } else {
+                    /* no locality active -> make this one active now */
+                    active_locty = locty;
+                }
+            }
+        }
+
+        if (set_new_locty) {
+            tis_new_active_locality(s, active_locty);
+        }
+
+        break;
+    case TIS_REG_INT_ENABLE:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        s->loc[locty].inte = (val & (INT_ENABLED | (0x3 << 3) |
+                                     INTERRUPTS_SUPPORTED));
+        break;
+    case TIS_REG_INT_VECTOR:
+        /* hard wired -- ignore */
+        break;
+    case TIS_REG_INT_STATUS:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        /* clearing of interrupt flags */
+        if (((val & INTERRUPTS_SUPPORTED)) &&
+            (s->loc[locty].ints & INTERRUPTS_SUPPORTED)) {
+            s->loc[locty].ints &= ~val;
+            if (s->loc[locty].ints == 0) {
+                qemu_irq_lower(s->irq);
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: Lowering IRQ\n");
+#endif
+            }
+        }
+        s->loc[locty].ints &= ~(val & INTERRUPTS_SUPPORTED);
+        break;
+    case TIS_REG_STS:
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        val &= (STS_COMMAND_READY | STS_TPM_GO | STS_RESPONSE_RETRY);
+
+        if (val == STS_COMMAND_READY) {
+            switch (s->loc[locty].state) {
+
+            case STATE_READY:
+                s->loc[locty].w_offset = 0;
+                s->loc[locty].r_offset = 0;
+            break;
+
+            case STATE_IDLE:
+                s->loc[locty].sts   = STS_COMMAND_READY;
+                s->loc[locty].state = STATE_READY;
+                tis_raise_irq(s, locty, INT_COMMAND_READY);
+            break;
+
+            case STATE_EXECUTION:
+            case STATE_RECEPTION:
+                /* abort currently running command */
+#ifdef DEBUG_TIS
+                fprintf(stderr, "tpm_tis: %s: Initiating abort.\n",
+                        __func__);
+#endif
+                tis_prep_abort(s, locty, locty);
+            break;
+
+            case STATE_COMPLETION:
+                s->loc[locty].w_offset = 0;
+                s->loc[locty].r_offset = 0;
+                /* shortcut to ready state with C/R set */
+                s->loc[locty].state = STATE_READY;
+                if (!(s->loc[locty].sts & STS_COMMAND_READY)) {
+                    s->loc[locty].sts   = STS_COMMAND_READY;
+                    tis_raise_irq(s, locty, INT_COMMAND_READY);
+                }
+            break;
+
+            }
+        } else if (val == STS_TPM_GO) {
+            switch (s->loc[locty].state) {
+            case STATE_RECEPTION:
+                tis_tpm_send(s, locty);
+                break;
+            default:
+                /* ignore */
+                break;
+            }
+        } else if (val == STS_RESPONSE_RETRY) {
+            switch (s->loc[locty].state) {
+            case STATE_COMPLETION:
+                s->loc[locty].r_offset = 0;
+                s->loc[locty].sts = STS_VALID | STS_DATA_AVAILABLE;
+                break;
+            default:
+                /* ignore */
+                break;
+            }
+        }
+        break;
+    case TIS_REG_DATA_FIFO:
+        /* data fifo */
+        if (s->active_locty != locty) {
+            break;
+        }
+
+        if (s->loc[locty].state == STATE_IDLE ||
+            s->loc[locty].state == STATE_EXECUTION ||
+            s->loc[locty].state == STATE_COMPLETION) {
+            /* drop the byte */
+        } else {
+#ifdef DEBUG_TIS
+            fprintf(stderr, "tpm_tis: Byte to send to TPM: %02x\n", val);
+#endif
+            if (s->loc[locty].state == STATE_READY) {
+                s->loc[locty].state = STATE_RECEPTION;
+                s->loc[locty].sts = STS_EXPECT | STS_VALID;
+            }
+
+            if ((s->loc[locty].sts & STS_EXPECT)) {
+                if (s->loc[locty].w_offset < s->loc[locty].w_buffer.size) {
+                    s->loc[locty].w_buffer.buffer[s->loc[locty].w_offset++] =
+                        (uint8_t)val;
+                } else {
+                    s->loc[locty].sts = STS_VALID;
+                }
+            }
+
+            /* check for complete packet */
+            if (s->loc[locty].w_offset > 5 &&
+                (s->loc[locty].sts & STS_EXPECT)) {
+                /* we have a packet length - see if we have all of it */
+#ifdef RAISE_STS_IRQ
+                bool needIrq = !(s->loc[locty].sts & STS_VALID);
+#endif
+                len = tis_get_size_from_buffer(&s->loc[locty].w_buffer);
+                if (len > s->loc[locty].w_offset) {
+                    s->loc[locty].sts = STS_EXPECT | STS_VALID;
+                } else {
+                    /* packet complete */
+                    s->loc[locty].sts = STS_VALID;
+                }
+#ifdef RAISE_STS_IRQ
+                if (needIrq) {
+                    tis_raise_irq(s, locty, INT_STS_VALID);
+                }
+#endif
+            }
+        }
+        break;
+    }
+
+    qemu_mutex_unlock(&s->state_lock);
+}
+
+
+static void tis_mem_writel(void *opaque, target_phys_addr_t addr,
+                           uint32_t val)
+{
+    return tis_mem_writel_intern(opaque, addr, val, false);
+}
+
+
+static CPUReadMemoryFunc *tis_readfn[3] = {
+    tis_mem_readl,
+    tis_mem_readl,
+    tis_mem_readl
+};
+
+static CPUWriteMemoryFunc *tis_writefn[3] = {
+    tis_mem_writel,
+    tis_mem_writel,
+    tis_mem_writel
+};
+
+
+/*
+ * This function gets called when resuming a snapshot. In that
+ * case we received the TIS state from persistent storage and
+ * just need to reset.
+ */
+void tis_reset_for_snapshot_resume(TPMState *s)
+{
+    s->tpm_initialized = false;
+    s->be_driver->ops->reset();
+    /* early startup not possible here */
+}
+
+
+static int tis_do_early_startup_tpm(TPMState *s)
+{
+    int rc;
+    /*
+     * Attempt an early startup of the backend; this only works
+     * if the block storage is not encrypted since the key is not
+     * available yet.
+     */
+    rc = s->be_driver->ops->early_startup_tpm();
+
+    switch (rc) {
+    case 0:
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Early startup worked -- "
+                "TPM backend is initialized\n");
+#endif
+        s->tpm_initialized = true;
+        break;
+
+    case -ENOKEY:
+#ifdef DEBUG_TIS
+        fprintf(stderr, "tpm_tis: Early startup failed -- "
+                "no key for encrypted drive\n");
+#endif
+        break;
+
+    default:
+        break;
+    }
+
+    return rc;
+}
+
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ */
+static void tis_s_reset(TPMState *s)
+{
+    int c;
+
+    s->tpm_initialized = false;
+
+    s->be_driver->ops->reset();
+
+    s->active_locty = NO_LOCALITY;
+    s->next_locty = NO_LOCALITY;
+    s->aborting_locty = NO_LOCALITY;
+
+    for (c = 0; c < NUM_LOCALITIES; c++) {
+        s->loc[c].access = ACCESS_TPM_REG_VALID_STS;
+        s->loc[c].sts = 0;
+        s->loc[c].inte = (1 << 3);
+        s->loc[c].ints = 0;
+        s->loc[c].state = STATE_IDLE;
+
+        s->loc[c].w_offset = 0;
+        s->be_driver->ops->realloc_buffer(&s->loc[c].w_buffer);
+        s->loc[c].r_offset = 0;
+        s->be_driver->ops->realloc_buffer(&s->loc[c].r_buffer);
+    }
+
+    tis_do_early_startup_tpm(s);
+}
+
+
+static void tis_reset(DeviceState *d)
+{
+    TPMState *s = container_of(d, TPMState, busdev.qdev);
+    tis_s_reset(s);
+}
+
+
+static int tis_init(ISADevice *dev)
+{
+    TPMState *s = DO_UPCAST(TPMState, busdev, dev);
+    int iomemtype, rc;
+    const char *backend = s->backend ? s->backend : TPM_DEFAULT_DEVICE_ID;
+
+    qemu_mutex_init(&s->state_lock);
+    qemu_cond_init(&s->from_tpm_cond);
+    qemu_cond_init(&s->to_tpm_cond);
+
+    s->be_driver = qemu_find_tpm(backend);
+    if (!s->be_driver) {
+        fprintf(stderr,
+                "tpm_tis: backend driver with id %s could not be found.n\n",
+                backend);
+        return -1;
+    }
+
+    if (s->be_driver->ops->init(s, tis_tpm_receive_cb)) {
+        goto err_exit;
+    }
+
+    isa_init_irq(dev, &s->irq, s->irq_num);
+
+    iomemtype = cpu_register_io_memory(tis_readfn, tis_writefn, s,
+                                       DEVICE_LITTLE_ENDIAN);
+    cpu_register_physical_memory(TIS_ADDR_BASE, 0x1000 * NUM_LOCALITIES,
+                                 iomemtype);
+
+    /*
+     * startup the TPM backend early to detect problems early
+     */
+    rc = tis_do_early_startup_tpm(s);
+    if (rc != 0 && rc != -ENOKEY) {
+        fprintf(stderr,
+                "tpm_tis: Fatal error accessing TPM's block storage.\n");
+        goto err_exit;
+    }
+
+    return 0;
+
+ err_exit:
+    return -1;
+}

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-01 17:20   ` Michael S. Tsirkin
  2011-09-09 21:13   ` Paul Moore
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process Stefan Berger
                   ` (11 subsequent siblings)
  14 siblings, 2 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_tis_persist.diff --]
[-- Type: text/plain, Size: 6515 bytes --]

This patch adds support for handling of persistent state to the TPM TIS
frontend.

The currently used buffer is determined (can only be in currently active
locality and either be a read or a write buffer) and only that buffer's content
is stored. The reverse is done when the state is restored from disk
where the buffer's content are copied into the currently used buffer.

To keep compatibility with existing Xen implementation the VMStateDescription
was adapted to be compatible with existing state. For that I am adding Andreas
Niederl as an author to the file.

v5:
 - removing qdev.no_user=1

v4:
 - main thread releases the 'state' lock while periodically calling the
   backends function that may request it to write data into block storage.

v3:
 - all functions prefixed with tis_
 - while the main thread is waiting for an outstanding TPM command to finish,
   it periodically does some work (writes data to the block storage)

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

---
 hw/tpm_tis.c |  166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 166 insertions(+)

Index: qemu-git/hw/tpm_tis.c
===================================================================
--- qemu-git.orig/hw/tpm_tis.c
+++ qemu-git/hw/tpm_tis.c
@@ -6,6 +6,8 @@
  * Author: Stefan Berger <stefanb@us.ibm.com>
  *         David Safford <safford@us.ibm.com>
  *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
  * published by the Free Software Foundation, version 2 of the
@@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
  err_exit:
     return -1;
 }
+
+/* persistent state handling */
+
+static void tis_pre_save(void *opaque)
+{
+    TPMState *s = opaque;
+    uint8_t locty = s->active_locty;
+
+    qemu_mutex_lock(&s->state_lock);
+
+    /* wait for outstanding requests to complete */
+    if (IS_VALID_LOCTY(locty) && s->loc[locty].state == STATE_EXECUTION) {
+        if (!s->be_driver->ops->job_for_main_thread) {
+            qemu_cond_wait(&s->from_tpm_cond, &s->state_lock);
+        } else {
+            while (s->loc[locty].state == STATE_EXECUTION) {
+                qemu_mutex_unlock(&s->state_lock);
+
+                s->be_driver->ops->job_for_main_thread(NULL);
+                usleep(10000);
+
+                qemu_mutex_lock(&s->state_lock);
+            }
+        }
+    }
+
+#ifdef DEBUG_TIS_SR
+    fprintf(stderr,
+            "tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = %d\n",
+            s->loc[0].r_offset, s->loc[0].w_offset);
+    if (s->loc[0].r_offset) {
+        tis_dump_state(opaque, 0);
+    }
+#endif
+
+    qemu_mutex_unlock(&s->state_lock);
+
+    /* copy current active read or write buffer into the buffer
+       written to disk */
+    if (IS_VALID_LOCTY(locty)) {
+        switch (s->loc[locty].state) {
+        case STATE_RECEPTION:
+            memcpy(s->buf,
+                   s->loc[locty].w_buffer.buffer,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].w_buffer.size));
+            s->offset = s->loc[locty].w_offset;
+        break;
+        case STATE_COMPLETION:
+            memcpy(s->buf,
+                   s->loc[locty].r_buffer.buffer,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].r_buffer.size));
+            s->offset = s->loc[locty].r_offset;
+        break;
+        default:
+            /* leak nothing */
+            memset(s->buf, 0x0, sizeof(s->buf));
+        break;
+        }
+    }
+
+    s->be_driver->ops->save_volatile_data();
+}
+
+
+static int tis_post_load(void *opaque,
+                         int version_id __attribute__((unused)))
+{
+    TPMState *s = opaque;
+
+    uint8_t locty = s->active_locty;
+
+    if (IS_VALID_LOCTY(locty)) {
+        switch (s->loc[locty].state) {
+        case STATE_RECEPTION:
+            memcpy(s->loc[locty].w_buffer.buffer,
+                   s->buf,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].w_buffer.size));
+            s->loc[locty].w_offset = s->offset;
+        break;
+        case STATE_COMPLETION:
+            memcpy(s->loc[locty].r_buffer.buffer,
+                   s->buf,
+                   MIN(sizeof(s->buf),
+                       s->loc[locty].r_buffer.size));
+            s->loc[locty].r_offset = s->offset;
+        break;
+        default:
+        break;
+        }
+    }
+
+#ifdef DEBUG_TIS_SR
+    fprintf(stderr,
+            "tpm_tis: resume : locty 0 : r_offset = %d, w_offset = %d\n",
+            s->loc[0].r_offset, s->loc[0].w_offset);
+#endif
+
+    return s->be_driver->ops->load_volatile_data(s);
+}
+
+
+static const VMStateDescription vmstate_locty = {
+    .name = "loc",
+    .version_id = 1,
+    .minimum_version_id = 0,
+    .minimum_version_id_old = 0,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(state, TPMLocality),
+        VMSTATE_UINT32(inte, TPMLocality),
+        VMSTATE_UINT32(ints, TPMLocality),
+        VMSTATE_UINT8(access, TPMLocality),
+        VMSTATE_UINT8(sts, TPMLocality),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+
+static const VMStateDescription vmstate_tis = {
+    .name = "tpm",
+    .version_id = 1,
+    .minimum_version_id = 0,
+    .minimum_version_id_old = 0,
+    .pre_save  = tis_pre_save,
+    .post_load = tis_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(irq_num, TPMState),
+        VMSTATE_UINT32(offset, TPMState),
+        VMSTATE_BUFFER(buf, TPMState),
+        VMSTATE_UINT8(active_locty, TPMState),
+        VMSTATE_UINT8(aborting_locty, TPMState),
+        VMSTATE_UINT8(next_locty, TPMState),
+
+        VMSTATE_STRUCT_ARRAY(loc, TPMState, NUM_LOCALITIES, 1,
+                             vmstate_locty, TPMLocality),
+
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+
+static ISADeviceInfo tis_device_info = {
+    .init         = tis_init,
+    .qdev.name    = "tpm-tis",
+    .qdev.size    = sizeof(TPMState),
+    .qdev.vmsd    = &vmstate_tis,
+    .qdev.reset   = tis_reset,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("irq", TPMState,
+                           irq_num, TPM_TIS_IRQ),
+        DEFINE_PROP_STRING("tpmdev", TPMState, backend),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+
+static void tis_register_device(void)
+{
+    isa_qdev_register(&tis_device_info);
+}
+
+device_init(tis_register_device)

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (2 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-01 17:23   ` Michael S. Tsirkin
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 05/14] Add a debug register Stefan Berger
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_build.diff --]
[-- Type: text/plain, Size: 2784 bytes --]

The TPM interface (tpm_tis) needs to be explicitly enabled via
./configure --enable-tpm. This patch also restricts the building of the
TPM support to i386 and x86_64 targets since only there it is currently
supported. This prevents that one will end up with support for a frontend
but no available backend.

v3:
 - fixed and moved hunks in Makefile.target into right place

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

Index:qemu/Makefile.target
===================================================================
---
 Makefile.target |    1 +
 configure       |   20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)

Index: qemu-git/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -233,6 +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
 
 # shared objects
 obj-ppc-y = ppc.o
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -183,6 +183,7 @@ usb_redir=""
 opengl=""
 zlib="yes"
 guest_agent="yes"
+tpm="no"
 
 # parse CC options first
 for opt do
@@ -765,6 +766,8 @@ for opt do
   ;;
   --disable-guest-agent) guest_agent="no"
   ;;
+  --enable-tpm) tpm="yes"
+  ;;
   *) echo "ERROR: unknown option $opt"; show_help="yes"
   ;;
   esac
@@ -1044,6 +1047,7 @@ echo "  --disable-usb-redir      disable
 echo "  --enable-usb-redir       enable usb network redirection support"
 echo "  --disable-guest-agent    disable building of the QEMU Guest Agent"
 echo "  --enable-guest-agent     enable building of the QEMU Guest Agent"
+echo "  --enable-tpm             enable an emulated TPM"
 echo ""
 echo "NOTE: The object files are built at the place where configure is launched"
 exit 1
@@ -2731,6 +2735,7 @@ echo "nss used          $smartcard_nss"
 echo "usb net redir     $usb_redir"
 echo "OpenGL support    $opengl"
 echo "build guest agent $guest_agent"
+echo "TPM support       $tpm"
 
 if test "$sdl_too_old" = "yes"; then
 echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -3555,6 +3560,21 @@ if test "$gprof" = "yes" ; then
   fi
 fi
 
+if test "$tpm" = "yes"; then
+  has_tpm=0
+  if test "$target_softmmu" = "yes" ; then
+    case "$TARGET_BASE_ARCH" in
+    i386)
+      has_tpm=1
+    ;;
+    esac
+  fi
+
+  if test "$has_tpm" = "1"; then
+      echo "CONFIG_TPM=y" >> $config_host_mak
+  fi
+fi
+
 linker_script="-Wl,-T../config-host.ld -Wl,-T,\$(SRC_PATH)/\$(ARCH).ld"
 if test "$target_linux_user" = "yes" -o "$target_bsd_user" = "yes" ; then
   case "$ARCH" in

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 05/14] Add a debug register
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (3 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 06/14] Add a TPM backend skeleton implementation Stefan Berger
                   ` (9 subsequent siblings)
  14 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_tis_debugreg.diff --]
[-- Type: text/plain, Size: 3270 bytes --]

This patch uses the possibility to add a vendor-specific register and
adds a debug register useful for dumping the TIS's internal state. This
register is only active in a debug build (#define DEBUG_TIS).

v3:
 - all output goes to stderr

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

---
 hw/tpm_tis.c |   67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

Index: qemu-git/hw/tpm_tis.c
===================================================================
--- qemu-git.orig/hw/tpm_tis.c
+++ qemu-git/hw/tpm_tis.c
@@ -43,6 +43,8 @@
 #define TIS_REG_DID_VID               0xf00
 #define TIS_REG_RID                   0xf04
 
+/* vendor-specific registers */
+#define TIS_REG_DEBUG                 0xf90
 
 #define STS_VALID                    (1 << 7)
 #define STS_COMMAND_READY            (1 << 6)
@@ -316,6 +318,66 @@ static uint32_t tis_data_read(TPMState *
 }
 
 
+#ifdef DEBUG_TIS
+static void tis_dump_state(void *opaque, target_phys_addr_t addr)
+{
+    static const unsigned regs[] = {
+        TIS_REG_ACCESS,
+        TIS_REG_INT_ENABLE,
+        TIS_REG_INT_VECTOR,
+        TIS_REG_INT_STATUS,
+        TIS_REG_INTF_CAPABILITY,
+        TIS_REG_STS,
+        TIS_REG_DID_VID,
+        TIS_REG_RID,
+        0xfff};
+    int idx;
+    uint8_t locty = tis_locality_from_addr(addr);
+    target_phys_addr_t base = addr & ~0xfff;
+    TPMState *s = opaque;
+
+    fprintf(stderr,
+            "tpm_tis: active locality      : %d\n"
+            "tpm_tis: state of locality %d : %d\n"
+            "tpm_tis: register dump:\n",
+            s->active_locty,
+            locty, s->loc[locty].state);
+
+    for (idx = 0; regs[idx] != 0xfff; idx++) {
+        fprintf(stderr, "tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
+                        tis_mem_readl(opaque, base + regs[idx]));
+    }
+
+    fprintf(stderr,
+            "tpm_tis: read offset   : %d\n"
+            "tpm_tis: result buffer : ",
+            s->loc[locty].r_offset);
+    for (idx = 0;
+         idx < tis_get_size_from_buffer(&s->loc[locty].r_buffer);
+         idx++) {
+        fprintf(stderr, "%c%02x%s",
+                s->loc[locty].r_offset == idx ? '>' : ' ',
+                s->loc[locty].r_buffer.buffer[idx],
+                ((idx & 0xf) == 0xf) ? "\ntpm_tis:                 " : "");
+    }
+    fprintf(stderr,
+            "\n"
+            "tpm_tis: write offset  : %d\n"
+            "tpm_tis: request buffer: ",
+            s->loc[locty].w_offset);
+    for (idx = 0;
+         idx < tis_get_size_from_buffer(&s->loc[locty].w_buffer);
+         idx++) {
+        fprintf(stderr, "%c%02x%s",
+                s->loc[locty].w_offset == idx ? '>' : ' ',
+                s->loc[locty].w_buffer.buffer[idx],
+                ((idx & 0xf) == 0xf) ? "\ntpm_tis:                 " : "");
+    }
+    fprintf(stderr, "\n");
+}
+#endif
+
+
 /*
  * Read a register of the TIS interface
  * See specs pages 33-63 for description of the registers
@@ -391,6 +453,11 @@ static uint32_t tis_mem_readl(void *opaq
     case TIS_REG_RID:
         val = TPM_RID;
         break;
+#ifdef DEBUG_TIS
+    case TIS_REG_DEBUG:
+        tis_dump_state(opaque, addr);
+        break;
+#endif
     }
 
     qemu_mutex_unlock(&s->state_lock);

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 06/14] Add a TPM backend skeleton implementation
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (4 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 05/14] Add a debug register Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend Stefan Berger
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_be_skeleton.diff --]
[-- Type: text/plain, Size: 15143 bytes --]

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(+)

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 = 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_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 = 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_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:
+    g_free(driver->id);
+    g_free(driver->model);
+    g_free(driver);
+    return NULL;
+}
+
+
+static void tpm_builtin_destroy(TPMBackend *driver)
+{
+    g_free(driver->id);
+    g_free(driver->model);
+    g_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 */

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (5 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 06/14] Add a TPM backend skeleton implementation Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-01 17:27   ` Michael S. Tsirkin
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer Stefan Berger
                   ` (7 subsequent siblings)
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_backend.diff --]
[-- Type: text/plain, Size: 22470 bytes --]

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 connects 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 is registered with
libtpms. Those callback functions are invoked by libtpms for example to
store the TPM's state.

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.

v6:
  - cache a copy of the last permanent state blob
  - move some functions into tpm_builtin.h
  - 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.
  - add out_len variable used in the thread

v5:
  - check access() to TPM's state file and report error if file is not
    accessible

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 <stefanb@linux.vnet.ibm.com>

---
 configure        |    1 
 hw/tpm_builtin.c |  450 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 hw/tpm_builtin.h |   56 ++++++
 3 files changed, 482 insertions(+), 25 deletions(-)

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,17 +18,36 @@
  * License along with this library; if not, see <http://www.gnu.org/licenses/>
  */
 
+#include "tpm_builtin.h"
+#include "blockdev.h"
+#include "block_int.h"
 #include "qemu-common.h"
-#include "tpm.h"
 #include "hw/hw.h"
 #include "hw/tpm_tis.h"
 #include "hw/pc.h"
+#include "migration.h"
+#include "sysemu.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>
 
 
 //#define DEBUG_TPM
 //#define DEBUG_TPM_SR /* suspend - resume */
 
 
+#define SAVESTATE_TYPE 'S'
+#define PERMSTATE_TYPE 'P'
+#define VOLASTATE_TYPE 'V'
+
+#define VTPM_DRIVE  "drive-vtpm0-nvram"
+#define TPM_OPTS "id=" VTPM_DRIVE
+
 /* data structures */
 
 typedef struct ThreadParams {
@@ -44,12 +63,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;
+static TPMSizedBuffer volatile_state;
+static TPMSizedBuffer save_state;
+static int pipefd[2] = {-1, -1};
 
 static bool thread_terminate;
 static bool tpm_initialized;
 static bool had_fatal_error;
 static bool had_startup_error;
 static bool thread_running;
+static bool need_read_volatile;
 
 static ThreadParams tpm_thread_params;
 
@@ -63,11 +88,23 @@ 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;
+    uint32_t in_len, out_len;
     uint8_t *in, *out;
     uint32_t resp_size; /* total length of response */
 
@@ -75,9 +112,11 @@ 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
     } else {
@@ -94,6 +133,8 @@ static void *tpm_builtin_main_loop(void 
         in_len = 0;
         do {
 #ifdef DEBUG_TPM
+            /* TPMLIB output goes to stdout */
+            fflush(stdout);
             fprintf(stderr, "tpm: waiting for commands...\n");
 #endif
 
@@ -125,6 +166,8 @@ static void *tpm_builtin_main_loop(void 
 
             if (tpm_initialized) {
 
+                out_len = thr_parms->tpm_state->loc[g_locty].r_buffer.size;
+
 #ifdef DEBUG_TPM
                 fprintf(stderr,
                         "tpm: received %d bytes from VM in locality %d\n",
@@ -135,17 +178,26 @@ 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);
+
+#ifdef DEBUG_TPM
+                fflush(stdout);
+#endif
 
-                if (res != 0) {
+                if (res != TPM_SUCCESS) {
 #ifdef DEBUG_TPM
-                    fprintf(stderr,
-                            "tpm: Sending/receiving TPM request/response "
-                            "failed.\n");
+                    fprintf(stderr, "tpm: TPMLIB_Process() failed\n");
 #endif
-                    had_fatal_error = true;
+                    /* we didn't get a response from the TPM; so
+                       construct one ourselves */
+                    goto send_err_response;
                 }
             } else {
+send_err_response:
                 out = thr_parms->tpm_state->loc[g_locty].r_buffer.buffer;
 
                 resp_size = sizeof(tpm_std_fatal_error_response);
@@ -168,11 +220,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
 
@@ -203,7 +257,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);
         }
 
@@ -215,6 +269,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;
 }
 
 
@@ -238,9 +298,23 @@ 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)
 {
-    return tpm_builtin_startup_tpm();
+    int rc;
+
+    rc = tpm_builtin_startup_bs(bs, fail_on_encrypted_drive);
+    if (rc) {
+        had_startup_error = true;
+        return rc;
+    }
+
+    tpm_builtin_load_tpm_state_from_bs(bs);
+
+    rc = tpm_builtin_startup_tpm();
+    if (rc) {
+        had_startup_error = true;
+    }
+    return rc;
 }
 
 
@@ -250,7 +324,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);
 }
 
 
@@ -266,7 +340,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_startup_error = true;
     }
 
@@ -274,6 +348,222 @@ 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 (had_fatal_error) {
+        return TPM_FAIL;
+    }
+
+#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 a copy of the last permanent state */
+            rc = TPM_Malloc(data, *length);
+            if (rc == 0) {
+                memcpy(*data, permanent_state.buffer, *length);
+            }
+        }
+    } 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 %5d 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 (had_fatal_error) {
+        return TPM_FAIL;
+    }
+
+    if (!strcmp(name, TPM_PERMANENT_ALL_NAME)) {
+        tsb = &permanent_state;
+        /* keep a copy of the last permanent state */
+        if (copy_sized_buffer(tsb, (unsigned char *)data, length) !=
+            TPM_SUCCESS) {
+                had_fatal_error = TRUE;
+                goto err_exit;
+        }
+
+#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;
+
+        set_sized_buffer(tsb, (unsigned char *)data, length);
+
+#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);
+
+        if (tpm_builtin_request_sync_to_bs(what)) {
+            rc = TPM_FAIL;
+        }
+
+        if (what == SAVESTATE_TYPE) {
+            /* TPM library will free */
+            tsb->buffer = NULL;
+            tsb->size = 0;
+        }
+
+        qemu_mutex_unlock(&state_mutex);
+    }
+
+err_exit:
+    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;
+
+    if (had_fatal_error) {
+        return TPM_FAIL;
+    }
+
+    /* only handle the savestate here */
+    if (!strcmp(name, TPM_SAVESTATE_NAME)) {
+        qemu_mutex_lock(&state_mutex);
+
+        clear_sized_buffer(&save_state);
+
+        if (tpm_builtin_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 (tpm_builtin_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
@@ -282,7 +572,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;
 }
 
@@ -313,27 +607,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_DRIVE);
+    if (bs == NULL) {
+        fprintf(stderr, "The " VTPM_DRIVE " driver was not found.\n");
+        goto err_exit;
+    }
+
+    if (TPMLIB_RegisterCallbacks(&callbacks) != TPM_SUCCESS) {
+        goto err_exit;
+    }
+
+    if (tpm_builtin_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;
 }
 
 
@@ -349,6 +711,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.
@@ -356,17 +722,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 (tpm_builtin_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 = g_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;
@@ -382,7 +777,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;
     }
 
@@ -409,7 +805,13 @@ static TPMBackend *tpm_builtin_create(Qe
 
     value = qemu_opt_get(opts, "path");
     if (value) {
-        /* !!! handle file path */
+        if (access(value, R_OK|W_OK)) {
+            fprintf(stderr,
+                    "Cannot access file '%s' from tpm's path option.\n",
+                    value);
+            goto err_exit;
+        }
+        drive_add(IF_NONE, -1, value, TPM_OPTS);
     } else {
         fprintf(stderr, "-tpm is missing path= parameter\n");
         goto err_exit;
@@ -436,7 +838,7 @@ static void tpm_builtin_destroy(TPMBacke
 TPMDriverOps 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,
     .create                   = tpm_builtin_create,
     .destroy                  = tpm_builtin_destroy,
     .init                     = tpm_builtin_init,
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -3571,7 +3571,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
Index: qemu-git/hw/tpm_builtin.h
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_builtin.h
@@ -0,0 +1,56 @@
+/*
+ * tpm_builtin.h - include file for tpm_builtin.h
+ *
+ * Copyright (C) 2011 IBM Corporation
+ *
+ * Author: Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+#ifndef _HW_TPM_BUILTIN_H
+#define _HW_TPM_BUILTIN_H
+
+#include "tpm.h"
+#include "tpm_tis.h"
+
+#include <libtpms/tpm_types.h>
+#include <libtpms/tpm_memory.h>
+#include <libtpms/tpm_error.h>
+
+static inline void clear_sized_buffer(TPMSizedBuffer *tpmsb)
+{
+    if (tpmsb->buffer) {
+        tpmsb->size = 0;
+        g_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;
+}
+
+static inline TPM_RESULT copy_sized_buffer(TPMSizedBuffer *tpmsb,
+                                           uint8_t *buffer, uint32_t size)
+{
+    TPM_RESULT rc;
+
+    rc = TPM_Realloc(&tpmsb->buffer, size);
+
+    if (rc == TPM_SUCCESS) {
+        tpmsb->size = size;
+        memcpy(tpmsb->buffer, buffer, size);
+    }
+
+    return rc;
+}
+
+
+#endif /* _HW_TPM_BUILTIN_H */

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (6 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend Stefan Berger
@ 2011-08-31 14:35 ` Stefan Berger
  2011-09-01 17:32   ` Michael S. Tsirkin
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 09/14] Add block storage support for libtpms based TPM backend Stefan Berger
                   ` (6 subsequent siblings)
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:35 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_bdrv_lock.diff --]
[-- Type: text/plain, Size: 7266 bytes --]

This patch introduces file locking via fcntl() for the block layer so that
concurrent access to files shared by 2 Qemu instances, for example via NFS,
can be serialized. This feature is useful primarily during initial phases of
VM migration where the target machine's TIS driver validates the block
storage (and in a later patch checks for missing AES keys) and terminates
Qemu if the storage is found to be faulty. This then allows migration to
be gracefully terminated and Qemu continues running on the source machine.

Support for win32 is based on win32 API and has been lightly tested with a
standalone test program locking shared storage from two different machines.

To enable locking a file multiple times, a counter is used. Actual locking
happens the very first time and unlocking happens when the counter is zero.

v7:
 - fixed compilation error in win32 part

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

---

---
 block.c           |   41 +++++++++++++++++++++++++++++++++++
 block.h           |    8 ++++++
 block/raw-posix.c |   63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 block/raw-win32.c |   52 ++++++++++++++++++++++++++++++++++++++++++++
 block_int.h       |    4 +++
 5 files changed, 168 insertions(+)

Index: qemu-git/block.c
===================================================================
--- qemu-git.orig/block.c
+++ qemu-git/block.c
@@ -521,6 +521,8 @@ static int bdrv_open_common(BlockDriverS
         goto free_and_fail;
     }
 
+    drv->num_locks = 0;
+
     bs->keep_read_only = bs->read_only = !(open_flags & BDRV_O_RDWR);
 
     ret = refresh_total_sectors(bs, bs->total_sectors);
@@ -1316,6 +1318,45 @@ void bdrv_get_geometry(BlockDriverState 
     *nb_sectors_ptr = length;
 }
 
+/* file locking */
+static int bdrv_lock_common(BlockDriverState *bs, BDRVLockType lock_type)
+{
+    BlockDriver *drv = bs->drv;
+
+    if (!drv) {
+        return -ENOMEDIUM;
+    }
+
+    if (bs->file) {
+        drv = bs->file->drv;
+        if (drv->bdrv_lock) {
+            return drv->bdrv_lock(bs->file, lock_type);
+        }
+    }
+
+    if (drv->bdrv_lock) {
+        return drv->bdrv_lock(bs, lock_type);
+    }
+
+    return -ENOTSUP;
+}
+
+
+int bdrv_lock(BlockDriverState *bs)
+{
+    if (bdrv_is_read_only(bs)) {
+        return bdrv_lock_common(bs, BDRV_F_RDLCK);
+    }
+
+    return bdrv_lock_common(bs, BDRV_F_WRLCK);
+}
+
+void bdrv_unlock(BlockDriverState *bs)
+{
+    bdrv_lock_common(bs, BDRV_F_UNLCK);
+}
+
+
 struct partition {
         uint8_t boot_ind;           /* 0x80 - active */
         uint8_t head;               /* starting head */
Index: qemu-git/block.h
===================================================================
--- qemu-git.orig/block.h
+++ qemu-git/block.h
@@ -43,6 +43,12 @@ typedef struct QEMUSnapshotInfo {
 #define BDRV_SECTOR_MASK   ~(BDRV_SECTOR_SIZE - 1)
 
 typedef enum {
+    BDRV_F_UNLCK,
+    BDRV_F_RDLCK,
+    BDRV_F_WRLCK,
+} BDRVLockType;
+
+typedef enum {
     BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC,
     BLOCK_ERR_STOP_ANY
 } BlockErrorAction;
@@ -100,6 +106,8 @@ int bdrv_commit(BlockDriverState *bs);
 void bdrv_commit_all(void);
 int bdrv_change_backing_file(BlockDriverState *bs,
     const char *backing_file, const char *backing_fmt);
+int bdrv_lock(BlockDriverState *bs);
+void bdrv_unlock(BlockDriverState *bs);
 void bdrv_register(BlockDriver *bdrv);
 
 
Index: qemu-git/block/raw-posix.c
===================================================================
--- qemu-git.orig/block/raw-posix.c
+++ qemu-git/block/raw-posix.c
@@ -803,6 +803,67 @@ static int64_t raw_get_allocated_file_si
     return (int64_t)st.st_blocks * 512;
 }
 
+static int raw_lock(BlockDriverState *bs, BDRVLockType lock_type)
+{
+    BlockDriver *drv = bs->drv;
+    BDRVRawState *s = bs->opaque;
+    struct flock flock = {
+        .l_whence = SEEK_SET,
+        .l_start = 0,
+        .l_len = 0,
+    };
+    int n;
+
+    switch (lock_type) {
+    case BDRV_F_RDLCK:
+    case BDRV_F_WRLCK:
+        if (drv->num_locks) {
+            drv->num_locks++;
+            return 0;
+        }
+        flock.l_type = (lock_type == BDRV_F_RDLCK) ? F_RDLCK : F_WRLCK;
+        break;
+
+    case BDRV_F_UNLCK:
+        if (--drv->num_locks > 0) {
+            return 0;
+        }
+
+        assert(drv->num_locks == 0);
+
+        flock.l_type = F_UNLCK;
+        break;
+
+    default:
+        return -EINVAL;
+    }
+
+    while (1) {
+        n = fcntl(s->fd, F_SETLKW, &flock);
+        if (n < 0) {
+            if (errno == EINTR) {
+                continue;
+            }
+            if (errno == EAGAIN) {
+                usleep(10000);
+                continue;
+            }
+        }
+        break;
+    }
+
+    if (n == 0 &&
+        ((lock_type == BDRV_F_RDLCK) || (lock_type == BDRV_F_WRLCK))) {
+        drv->num_locks = 1;
+    }
+
+    if (n) {
+        return -errno;
+    }
+
+    return 0;
+}
+
 static int raw_create(const char *filename, QEMUOptionParameter *options)
 {
     int fd;
@@ -901,6 +962,8 @@ static BlockDriver bdrv_file = {
     .bdrv_get_allocated_file_size
                         = raw_get_allocated_file_size,
 
+    .bdrv_lock = raw_lock,
+
     .create_options = raw_create_options,
 };
 
Index: qemu-git/block_int.h
===================================================================
--- qemu-git.orig/block_int.h
+++ qemu-git/block_int.h
@@ -146,6 +146,10 @@ struct BlockDriver {
      */
     int (*bdrv_has_zero_init)(BlockDriverState *bs);
 
+    /* File locking */
+    int num_locks;
+    int (*bdrv_lock)(BlockDriverState *bs, BDRVLockType lock_type);
+
     QLIST_ENTRY(BlockDriver) list;
 };
 
Index: qemu-git/block/raw-win32.c
===================================================================
--- qemu-git.orig/block/raw-win32.c
+++ qemu-git/block/raw-win32.c
@@ -242,6 +242,57 @@ static int64_t raw_get_allocated_file_si
     return st.st_size;
 }
 
+static int raw_lock(BlockDriverState *bs, int lock_type)
+{
+    BlockDriver *drv = bs->drv;
+    BDRVRawState *s = bs->opaque;
+    OVERLAPPED ov;
+    BOOL res;
+    DWORD num_bytes;
+
+    switch (lock_type) {
+    case BDRV_F_RDLCK:
+    case BDRV_F_WRLCK:
+        if (drv->num_locks) {
+            drv->num_locks++;
+            return 0;
+        }
+
+        memset(&ov, 0, sizeof(ov));
+
+        res = LockFileEx(s->hfile, LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, ~0, &ov);
+
+        if (res == FALSE) {
+            res = GetOverlappedResult(s->hfile, &ov, &num_bytes, TRUE);
+        }
+
+        if (res == TRUE) {
+            drv->num_locks = 1;
+        }
+
+        break;
+
+    case BDRV_F_UNLCK:
+        if (--drv->num_locks > 0) {
+            return 0;
+        }
+
+        assert(drv->num_locks >= 0);
+
+        res = UnlockFile(s->hfile, 0, 0, ~0, ~0);
+        break;
+
+    default:
+        return -EINVAL;
+    }
+
+    if (res == FALSE) {
+        return -EIO;
+    }
+
+    return 0;
+}
+
 static int raw_create(const char *filename, QEMUOptionParameter *options)
 {
     int fd;
@@ -289,6 +340,7 @@ static BlockDriver bdrv_file = {
     .bdrv_get_allocated_file_size
                         = raw_get_allocated_file_size,
 
+    .bdrv_lock		= raw_lock,
     .create_options = raw_create_options,
 };
 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 09/14] Add block storage support for libtpms based TPM backend
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (7 preceding siblings ...)
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption Stefan Berger
                   ` (5 subsequent siblings)
  14 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_backend_bs.diff --]
[-- Type: text/plain, Size: 25576 bytes --]

This patch adds support for storing the TPM's persistent state into Qemu
block storage, i.e., QCoW2.

The TPM creates state of varying size, depending for example on how many
keys are loaded into it at a certain time. The worst-case sizes of
the different blobs the TPM can write have been pre-calculated and this
value is used to determine the minimum size of the Qcow2 image. It needs to
be 83kb (libtpm rev. 7). '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. 

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

The entries are described through their absolute offsets, their maximum
sizes, the number of currently valid bytes (the blobs inflate and deflate)
and what type of blob it is (see below for the types). A CRC32 over the blob
is also included.

typedef struct BSEntry {
    enum BSEntryType type;
    uint64_t offset;
    uint32_t space;
    uint32_t blobsize;
    uint32_t blobcrc32;
    uint32_t reserved[9];
} __attribute__((packed)) BSEntry;


The worst case sizes of the blobs have been calculated and according to the
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() before entering ACPI S3.

The 'permanent state' is written when the TPM receives a command that alters
its permenent state, i.e., when 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 QCoW2 storage where certain
operations need to be deferred since the key for the storage only becomes
available much later via the monitor 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 Qemu does not exit, but the TPM is put
into failure mode.

v6:
  - 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.
  - removed data layout function
  - only initializing storage directory if it is found to be empty; report
    error if found corrupted
  - removed some assert()s

v5:
  - name of drive is 'drive-vtpm0-nvram'; was 'vtpm-nvram'

v4:
  - functions prefixed with tpm_builtin
  - added 10 uint32_t to BSDir as being reserved for future use
  - never move data in the block storage while migration is going on
  - use brdv_lock/bdrv_unlock to serialize access to the TPM's state
    file which is primarily necessary during migration and the startup
    of qemu on the target host where the content of the drive is being
    read and validated

v3:
  - added reserved int's for future extensions to the entries in the
    directory structure
  - added crc32 to every entry in the directory structure and calculating
    it when writing and checking it when reading
  - fixed an endianess issue related to crc calculation
  - surrounding debugging output function in adjust_data_layout
    with #if defined DEBUG_TPM
  - probing for installed libtpms development package by test-compiling

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

---
 configure        |   25 +
 hw/tpm_builtin.c |  706 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 731 insertions(+)

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -48,6 +48,34 @@
 #define VTPM_DRIVE  "drive-vtpm0-nvram"
 #define TPM_OPTS "id=" VTPM_DRIVE
 
+
+#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 {
@@ -57,6 +85,40 @@ typedef struct ThreadParams {
 } ThreadParams;
 
 
+enum BSEntryType {
+    BS_ENTRY_PERMSTATE,
+    BS_ENTRY_SAVESTATE,
+    BS_ENTRY_VOLASTATE,
+
+    BS_ENTRY_LAST,
+};
+
+
+typedef struct BSEntry {
+    enum BSEntryType type;
+    uint64_t offset;
+    uint32_t space;
+    uint32_t blobsize;
+    uint32_t blobcrc32;
+    uint32_t reserved[9];
+} __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;
+    uint32_t  reserved[10];
+    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;
@@ -77,6 +139,7 @@ static bool thread_running;
 static bool need_read_volatile;
 
 static ThreadParams tpm_thread_params;
+static BlockDriverState *bs;
 
 /* locality of the command being executed by libtpms */
 static uint8_t g_locty;
@@ -88,6 +151,12 @@ static const unsigned char tpm_std_fatal
 static char dev_description[80];
 
 
+static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
+                                               enum BSEntryType be,
+                                               TPMSizedBuffer *tsb);
+
+
+
 static int tpmlib_get_prop(enum TPMLIB_TPMProperty prop)
 {
     int result;
@@ -100,6 +169,643 @@ static int tpmlib_get_prop(enum TPMLIB_T
 }
 
 
+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 tpm_builtin_find_bs_entry_idx(BSDir *dir, enum BSEntryType type)
+{
+    unsigned int c;
+
+    for (c = 0; c < dir->num_entries; c++) {
+        if (dir->entries[c].type == type) {
+            return c;
+        }
+    }
+
+    return -ENOENT;
+}
+
+
+static void tpm_builtin_dir_be_to_cpu(BSDir *dir)
+{
+    unsigned int c;
+
+    be16_to_cpus(&dir->rev);
+    be32_to_cpus(&dir->checksum);
+    be32_to_cpus(&dir->num_entries);
+
+    for (c = 0; c < dir->num_entries && c < BS_DIR_MAX_NUM_ENTRIES; c++) {
+        be32_to_cpus(&dir->entries[c].type);
+        be64_to_cpus(&dir->entries[c].offset);
+        be32_to_cpus(&dir->entries[c].space);
+        be32_to_cpus(&dir->entries[c].blobsize);
+        be32_to_cpus(&dir->entries[c].blobcrc32);
+    }
+}
+
+
+static void tpm_builtin_dir_cpu_to_be(BSDir *dir)
+{
+    unsigned int c;
+
+    for (c = 0; c < dir->num_entries && c < BS_DIR_MAX_NUM_ENTRIES; c++) {
+        dir->entries[c].type      = cpu_to_be32(dir->entries[c].type);
+        dir->entries[c].offset    = cpu_to_be64(dir->entries[c].offset);
+        dir->entries[c].space     = cpu_to_be32(dir->entries[c].space);
+        dir->entries[c].blobsize  = cpu_to_be32(dir->entries[c].blobsize);
+        dir->entries[c].blobcrc32 = cpu_to_be32(dir->entries[c].blobcrc32);
+    }
+
+    dir->rev         = cpu_to_be16(dir->rev);
+    dir->checksum    = cpu_to_be32(dir->checksum);
+    dir->num_entries = cpu_to_be32(dir->num_entries);
+}
+
+
+static unsigned int tpm_builtin_sizeof_bsdir(BSDir *dir)
+{
+    return offsetof(BSDir, entries) +
+           dir->num_entries * sizeof(BSEntry);
+}
+
+
+static uint32_t tpm_builtin_calc_dir_checksum(BSDir *dir)
+{
+    uint16_t checksum, orig;
+    unsigned int bsdir_size = tpm_builtin_sizeof_bsdir(dir);
+
+    orig = dir->checksum;
+    dir->checksum = 0;
+
+    /* normalize to big endian */
+    tpm_builtin_dir_cpu_to_be(dir);
+
+    checksum = crc32(0, (unsigned char *)dir, bsdir_size);
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    dir->checksum = orig;
+
+    return checksum;
+}
+
+
+static bool tpm_builtin_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 == tpm_builtin_calc_dir_checksum(dir));
+}
+
+
+static bool tpm_builtin_has_valid_content(BSDir *dir)
+{
+    bool rc = true;
+    uint32_t c;
+    TPMSizedBuffer tsb = {
+        .buffer = NULL,
+        .size = 0
+    };
+
+    for (c = 0; c < dir->num_entries; c++) {
+        if (tpm_builtin_load_sized_data_from_bs(bs,
+                                                dir->entries[c].type,
+                                                &tsb) != 0) {
+            rc = false;
+            break;
+        }
+
+        clear_sized_buffer(&tsb);
+    }
+
+    return rc;
+}
+
+
+static int tpm_builtin_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 = tpm_builtin_calc_dir_checksum(dir);
+
+    tpm_builtin_dir_cpu_to_be(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 tpm_builtin_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;
+}
+
+
+static uint32_t tpm_builtin_get_bs_entry_type_space(enum BSEntryType type)
+{
+    switch (type) {
+    case BS_ENTRY_PERMSTATE:
+        return PERMSTATE_DISK_SPACE;
+    case BS_ENTRY_SAVESTATE:
+        return SAVESTATE_DISK_SPACE;
+    case BS_ENTRY_VOLASTATE:
+        return VOLASTATE_DISK_SPACE;
+    default:
+        assert(false);
+    }
+}
+
+
+/*
+ * 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 tpm_builtin_startup_bs(BlockDriverState *bs,
+                                  bool fail_on_encrypted_drive)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+    int rc = 0;
+
+    if (bdrv_lock(bs)) {
+        return -EIO;
+    }
+
+    if (bdrv_read(bs, 0, buf, 1) < 0) {
+        had_fatal_error = true;
+        rc = -EIO;
+        goto err_exit;
+    }
+
+    dir = (BSDir *)buf;
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    if (!tpm_builtin_is_valid_bsdir(dir) ||
+        !tpm_builtin_has_valid_content(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 (fail_on_encrypted_drive) {
+                rc = -ENOKEY;
+                goto err_exit;
+            }
+            if (memsum(buf, sizeof(buf)) != 0) {
+                fprintf(stderr,
+                        "vTPM block storage directory is not valid. "
+                        "Assuming the key is wrong.\n");
+                had_fatal_error = true;
+                rc = -EKEYREJECTED;
+                goto err_exit;
+            }
+        }
+
+        if (incoming_expected) {
+            /* don't modify the dir in case of incoming migration */
+            rc = 0;
+            goto err_exit;
+        }
+
+        if (memsum(buf, sizeof(buf)) == 0) {
+#ifdef DEBUG_TPM
+            fprintf(stderr,
+                    "tpm: Initializing the TPM's storage directory.\n");
+#endif
+            rc = tpm_builtin_create_blank_dir(bs);
+            if (rc != 0) {
+                fprintf(stderr, "tpm: Could not initialize TPM storage.\n");
+                had_fatal_error =  true;
+            }
+        } else {
+            fprintf(stderr, "tpm: TPM storage is corrupted.\n");
+            rc = -EIO;
+            had_fatal_error = true;
+        }
+    }
+
+err_exit:
+    bdrv_unlock(bs);
+
+    return rc;
+}
+
+
+static int tpm_builtin_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++;
+    unsigned int bsdir_size;
+
+    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;
+
+    dir->entries[idx].space = tpm_builtin_get_bs_entry_type_space(type);
+    dir->entries[idx].blobsize = blobsize;
+
+    dir->checksum = tpm_builtin_calc_dir_checksum(dir);
+
+    bsdir_size = tpm_builtin_sizeof_bsdir(dir);
+
+    tpm_builtin_dir_cpu_to_be(dir);
+
+    memset(buf, 0x0, sizeof(buf));
+    memcpy(buf, dir, bsdir_size);
+
+    if (bdrv_write(bs, 0, buf, 1) < 0) {
+        idx = -EIO;
+    }
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    return idx;
+}
+
+
+static int tpm_builtin_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;
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    idx = tpm_builtin_find_bs_entry_idx(dir, type);
+    if (idx < 0) {
+        idx = tpm_builtin_create_bs_entry(bs, dir, type, 0);
+        if (idx < 0) {
+            return -EIO;
+        }
+    }
+
+    memcpy(entry, &dir->entries[idx], sizeof(*entry));
+
+    return 0;
+}
+
+
+static int set_bs_entry_size_crc(BlockDriverState *bs,
+                                 enum BSEntryType type,
+                                 BSEntry *entry,
+                                 uint32_t blobsize,
+                                 uint32_t blobcrc32)
+{
+    uint8_t buf[BDRV_SECTOR_SIZE];
+    BSDir *dir;
+    int idx;
+
+    if (bdrv_read(bs, 0, buf, 1) < 0) {
+        return -EIO;
+    }
+
+    dir = (BSDir *)buf;
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    idx = tpm_builtin_find_bs_entry_idx(dir, type);
+    if (idx < 0) {
+        idx = tpm_builtin_create_bs_entry(bs, dir, type, 0);
+        if (idx < 0) {
+            return -EIO;
+        }
+    }
+
+    dir->entries[idx].blobsize  = blobsize;
+    dir->entries[idx].blobcrc32 = blobcrc32;
+
+    dir->checksum = tpm_builtin_calc_dir_checksum(dir);
+
+    tpm_builtin_dir_cpu_to_be(dir);
+
+    if (bdrv_write(bs, 0, buf, 1) < 0) {
+        return -EIO;
+    }
+
+    tpm_builtin_dir_be_to_cpu(dir);
+
+    memcpy(entry, &dir->entries[idx], sizeof(*entry));
+
+    return 0;
+}
+
+
+static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
+                                               enum BSEntryType be,
+                                               TPMSizedBuffer *tsb)
+{
+    BSEntry entry;
+    int rc;
+
+    if (bdrv_lock(bs)) {
+        return -EIO;
+    }
+
+    rc = tpm_builtin_get_bs_entry(bs, be, &entry);
+    if (rc < 0) {
+        goto err_exit;
+    }
+
+#if defined DEBUG_TPM || defined DEBUG_TPM_SR
+    fprintf(stderr, "tpm: load: be-type: %d, offset: %6ld, size: %5d\n",
+            be, (long int)entry.offset, entry.blobsize);
+#endif
+
+    if (entry.blobsize == 0) {
+        goto err_exit;
+    }
+
+    tsb->buffer = g_malloc(entry.blobsize);
+    if (!tsb->buffer) {
+        rc = -ENOMEM;
+        goto err_exit;
+    }
+
+    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 stored data!\n");
+        rc = -EIO;
+        goto err_exit;
+    }
+
+    if (entry.blobcrc32 != crc32(0, tsb->buffer, tsb->size)) {
+        fprintf(stderr, "tpm: CRC of stored data is wrong! : %x vs. %x\n",
+                entry.blobcrc32,
+                (int)crc32(0, tsb->buffer, tsb->size));
+        clear_sized_buffer(tsb);
+        rc = -EBADMSG;
+    }
+
+err_exit:
+    bdrv_unlock(bs);
+
+    return rc;
+}
+
+
+static int tpm_builtin_load_tpm_permanent_state_from_bs(BlockDriverState *bs,
+                                            TPMSizedBuffer *tsb)
+{
+    return tpm_builtin_load_sized_data_from_bs(bs, BS_ENTRY_PERMSTATE, tsb);
+}
+
+
+static int tpm_builtin_load_tpm_savestate_from_bs(BlockDriverState *bs,
+                                                  TPMSizedBuffer *tsb)
+{
+    return tpm_builtin_load_sized_data_from_bs(bs, BS_ENTRY_SAVESTATE, tsb);
+}
+
+
+static int tpm_builtin_load_tpm_volatile_state_from_bs(BlockDriverState *bs,
+                                                       TPMSizedBuffer *tsb)
+{
+    return tpm_builtin_load_sized_data_from_bs(bs, BS_ENTRY_VOLASTATE, tsb);
+}
+
+
+static int tpm_builtin_save_sized_data_to_bs(BlockDriverState *bs,
+                                             enum BSEntryType be,
+                                             uint8_t *data, uint32_t data_len)
+{
+    BSEntry entry;
+    int rc;
+    uint32_t crc = 0;
+
+    if (data_len > 0) {
+        crc = crc32(0, (unsigned char *)data, data_len);
+    }
+
+    if (bdrv_lock(bs)) {
+        return -EIO;
+    }
+
+    rc = set_bs_entry_size_crc(bs, be, &entry, data_len, crc);
+    if (rc < 0) {
+        goto err_exit;
+    }
+
+    if (data_len > 0) {
+        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len) {
+            rc = -EIO;
+        }
+    }
+
+err_exit:
+    bdrv_unlock(bs);
+
+    return rc;
+}
+
+
+/* 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 = tpm_builtin_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 tpm_builtin_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 tpm_builtin_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 tpm_builtin_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 write 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 tpm_builtin_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++) {
+            tpm_builtin_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 tpm_builtin_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;
+    }
+
+    qemu_cond_wait(&bs_write_result_cond, &state_mutex);
+
+    return had_fatal_error;
+}
+
+
+static void tpm_builtin_load_tpm_state_from_bs(BlockDriverState *bs)
+{
+    tpm_builtin_load_tpm_permanent_state_from_bs(bs, &permanent_state);
+    tpm_builtin_load_tpm_savestate_from_bs(bs, &save_state);
+
+    if (need_read_volatile) {
+        clear_sized_buffer(&volatile_state);
+        tpm_builtin_load_tpm_volatile_state_from_bs(bs, &volatile_state);
+        need_read_volatile = false;
+    }
+}
+
+
 static void *tpm_builtin_main_loop(void *d)
 {
     TPM_RESULT res;
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -2574,6 +2574,22 @@ EOF
 fi
 
 ##########################################
+# libtpms probe
+
+if test "$tpm" = "yes" ; then
+  cat > $TMPC <<EOF
+#include <libtpms/tpm_library.h>
+int main(void) { return (int)TPMLIB_GetVersion(); }
+EOF
+  libtpms=no
+  if compile_prog "" "-ltpms" ; then
+    libtpms=yes
+  else
+    tpm_need_pkgs="libtpms development package"
+  fi
+fi
+
+##########################################
 # End of CC checks
 # After here, no more $cc or $ld runs
 
@@ -3571,6 +3587,15 @@ if test "$tpm" = "yes"; then
   fi
 
   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
 fi

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (8 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 09/14] Add block storage support for libtpms based TPM backend Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-09-01 19:26   ` Michael S. Tsirkin
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 11/14] Experimental support for block migrating TPMs state Stefan Berger
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_backend_enc.diff --]
[-- Type: text/plain, Size: 17711 bytes --]

This patch adds encryption of the individual state blobs that are written
into the block storage. The 'directory' at the beginnig of the block
storage is not encrypted.

The encryption support added in this patch would also work if QCoW2 was not
to be used as the (only) image file format to store the TPM's state.

Keys can be passed as a string of hexadecimal digits forming a 256, 192 or
128 bit AES key. The string can optionally start with '0x'. If the
parser does not recognize it as a hexadecimal number, the string itself is
taken as the AES key, which makes for example 'my_key' a valid AES key
parameter. It is also necessary to provide the encryption scheme.
Currently only 'aes-cbc' is supported.  An example for a valid key command
line argument is:

-tpm builtin,key=aes-cbc:0x1234567890abcdef123456

The key passed via command line argument is wiped from the command
line after parsing. If for example key=aes-cbc:0x1234... was passed it will
then be changed to key=------... so that 'ps' does not show the key anymore.
Obviously it cannot be completely prevented that the key is visible during a
very short period of time until qemu gets to the point where the code wiping
the key is reached.

A byte indicating the encryption type being used is introduced in the
directory structure indicating whether blobs are encrypted and if so, what
encryption type was used, i.e., aes-cbc.

An additional 'layer' for reading and writing the blobs to the underlying
block storage is added. This layer encrypts the blobs for writing if a key is
available. Similarly it decrypts the blobs after reading.

Checks are added that test
- whether encryption is supported follwing the revision of the directory
  structure (rev >= 2)
- whether a key has been provided although all data are stored in clear-text
- whether a key is missing for decryption.

In either one of the cases the backend reports an error message to the user
and Qemu terminates.

-v7:
  - cleaned up function parsing key

-v6:
  - changed the format of the key= to take the type of encryption into
    account: key=aes-cbc:0x12345... and reworked code for encryption and
    decryption of blobs;
  - modified directory entry to hold a uint_8 describing the encryption
    type (none, aes-cbc) being used for the blobs.
  - incrementing revision of the directory to '2' indicating encryption
    support
    
-v5:
  - -tpmdev now also gets a key parameter
  - add documentation about key parameter

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

---
 hw/tpm_builtin.c |  285 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 qemu-config.c    |   10 +
 qemu-options.hx  |   22 +++-
 tpm.c            |   10 +
 4 files changed, 318 insertions(+), 9 deletions(-)

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -27,6 +27,7 @@
 #include "hw/pc.h"
 #include "migration.h"
 #include "sysemu.h"
+#include "aes.h"
 
 #include <libtpms/tpm_library.h>
 #include <libtpms/tpm_error.h>
@@ -110,14 +111,27 @@ typedef struct BSDir {
     uint16_t  rev;
     uint32_t  checksum;
     uint32_t  num_entries;
-    uint32_t  reserved[10];
+    uint8_t   enctype;
+    uint8_t   reserved1[3];
+    uint32_t  reserved[8];
     BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
 } __attribute__((packed)) BSDir;
 
 
 #define BS_DIR_REV1         1
+/* rev 2 added encryption */
+#define BS_DIR_REV2         2
 
-#define BS_DIR_REV_CURRENT  BS_DIR_REV1
+
+#define BS_DIR_REV_CURRENT  BS_DIR_REV2
+
+/* above enctype */
+enum BSEnctype {
+    BS_DIR_ENCTYPE_NONE = 0,
+    BS_DIR_ENCTYPE_AES_CBC,
+
+    BS_DIR_ENCTYPE_LAST,
+};
 
 /* local variables */
 
@@ -150,6 +164,11 @@ static const unsigned char tpm_std_fatal
 
 static char dev_description[80];
 
+static struct enckey {
+    uint8_t enctype;
+    AES_KEY tpm_enc_key;
+    AES_KEY tpm_dec_key;
+} enckey;
 
 static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
                                                enum BSEntryType be,
@@ -264,7 +283,7 @@ static uint32_t tpm_builtin_calc_dir_che
 
 static bool tpm_builtin_is_valid_bsdir(BSDir *dir)
 {
-    if (dir->rev != BS_DIR_REV_CURRENT ||
+    if (dir->rev > BS_DIR_REV_CURRENT ||
         dir->num_entries > BS_DIR_MAX_NUM_ENTRIES) {
         return false;
     }
@@ -295,6 +314,33 @@ static bool tpm_builtin_has_valid_conten
     return rc;
 }
 
+static bool tpm_builtin_supports_encryption(const BSDir *dir)
+{
+    return (dir->rev >= BS_DIR_REV2);
+}
+
+
+static bool tpm_builtin_has_missing_key(const BSDir *dir)
+{
+    return ((dir->enctype   != BS_DIR_ENCTYPE_NONE) &&
+            (enckey.enctype == BS_DIR_ENCTYPE_NONE));
+}
+
+
+static bool tpm_builtin_has_unnecessary_key(const BSDir *dir)
+{
+    return (((dir->enctype   == BS_DIR_ENCTYPE_NONE) &&
+             (enckey.enctype != BS_DIR_ENCTYPE_NONE)) ||
+            ((!tpm_builtin_supports_encryption(dir)) &&
+             (enckey.enctype != BS_DIR_ENCTYPE_NONE)));
+}
+
+
+static bool tpm_builtin_uses_unsupported_enctype(const BSDir *dir)
+{
+    return (dir->enctype >= BS_DIR_ENCTYPE_LAST);
+}
+
 
 static int tpm_builtin_create_blank_dir(BlockDriverState *bs)
 {
@@ -306,6 +352,7 @@ static int tpm_builtin_create_blank_dir(
     dir = (BSDir *)buf;
     dir->rev = BS_DIR_REV_CURRENT;
     dir->num_entries = 0;
+    dir->enctype = enckey.enctype;
 
     dir->checksum = tpm_builtin_calc_dir_checksum(dir);
 
@@ -407,6 +454,38 @@ static int tpm_builtin_startup_bs(BlockD
 
     tpm_builtin_dir_be_to_cpu(dir);
 
+    if (tpm_builtin_is_valid_bsdir(dir)) {
+        if (tpm_builtin_supports_encryption(dir) &&
+            tpm_builtin_has_missing_key(dir)) {
+            fprintf(stderr,
+                    "tpm: the data are encrypted but I am missing the key.\n");
+            rc = -EIO;
+            goto err_exit;
+        }
+        if (tpm_builtin_has_unnecessary_key(dir)) {
+            fprintf(stderr,
+                    "tpm: I have a key but the data are not encrypted.\n");
+            rc = -EIO;
+            goto err_exit;
+        }
+        if (tpm_builtin_supports_encryption(dir) &&
+            tpm_builtin_uses_unsupported_enctype(dir)) {
+            fprintf(stderr,
+                    "tpm: State is encrypted with an unsupported encryption "
+                    "scheme.\n");
+            rc = -EIO;
+            goto err_exit;
+        }
+        if (tpm_builtin_supports_encryption(dir) &&
+            (dir->enctype != BS_DIR_ENCTYPE_NONE) &&
+            !tpm_builtin_has_valid_content(dir)) {
+            fprintf(stderr, "tpm: cannot read the data - "
+                    "is this the wrong key?\n");
+            rc = -EIO;
+            goto err_exit;
+        }
+    }
+
     if (!tpm_builtin_is_valid_bsdir(dir) ||
         !tpm_builtin_has_valid_content(dir)) {
         /* if it's encrypted and has something else than null-content,
@@ -569,6 +648,105 @@ static int set_bs_entry_size_crc(BlockDr
 }
 
 
+static int tpm_builtin_blocksize_roundup(uint8_t enctype, int plainsize)
+{
+    switch (enctype) {
+    case BS_DIR_ENCTYPE_NONE:
+        return plainsize;
+    case BS_DIR_ENCTYPE_AES_CBC:
+        return ALIGN(plainsize, AES_BLOCK_SIZE);
+    default:
+        assert(false);
+        return 0;
+    }
+}
+
+
+static int tpm_builtin_bdrv_pread(BlockDriverState *bs, int64_t offset,
+                                  void *buf, int count,
+                                  enum BSEntryType type)
+{
+    int ret;
+    union {
+        uint64_t ll[2];
+        uint8_t b[16];
+    } ivec;
+    int toread = count;
+
+    toread = tpm_builtin_blocksize_roundup(enckey.enctype, count);
+
+    ret = bdrv_pread(bs, offset, buf, toread);
+
+    if (ret != toread) {
+        return ret;
+    }
+
+    switch (enckey.enctype) {
+    case BS_DIR_ENCTYPE_NONE:
+        break;
+    case BS_DIR_ENCTYPE_AES_CBC:
+        ivec.ll[0] = cpu_to_be64(type);
+        ivec.ll[1] = 0;
+
+        AES_cbc_encrypt(buf, buf, toread, &enckey.tpm_dec_key, ivec.b, 0);
+        break;
+    default:
+        assert(false);
+    }
+
+    return count;
+}
+
+
+static int tpm_builtin_bdrv_pwrite(BlockDriverState *bs, int64_t offset,
+                                   void *buf, int count,
+                                   enum BSEntryType type)
+{
+    int ret;
+    union {
+        uint64_t ll[2];
+        uint8_t b[16];
+    } ivec;
+    int towrite = count;
+    void *out_buf = buf;
+
+    switch (enckey.enctype) {
+    case BS_DIR_ENCTYPE_NONE:
+        break;
+    case BS_DIR_ENCTYPE_AES_CBC:
+        ivec.ll[0] = cpu_to_be64(type);
+        ivec.ll[1] = 0;
+
+        towrite = ALIGN(count, AES_BLOCK_SIZE);
+
+        if (towrite != count) {
+            out_buf = g_malloc(towrite);
+
+            if (out_buf == NULL) {
+                return -ENOMEM;
+            }
+        }
+
+        AES_cbc_encrypt(buf, out_buf, towrite, &enckey.tpm_enc_key, ivec.b, 1);
+        break;
+    default:
+        assert(false);
+    }
+
+    ret = bdrv_pwrite(bs, offset, out_buf, towrite);
+
+    if (out_buf != buf) {
+        g_free(out_buf);
+    }
+
+    if (ret == towrite) {
+        return count;
+    }
+
+    return ret;
+}
+
+
 static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
                                                enum BSEntryType be,
                                                TPMSizedBuffer *tsb)
@@ -594,7 +772,7 @@ static int tpm_builtin_load_sized_data_f
         goto err_exit;
     }
 
-    tsb->buffer = g_malloc(entry.blobsize);
+    tsb->buffer = g_malloc(ALIGN(entry.blobsize, AES_BLOCK_SIZE));
     if (!tsb->buffer) {
         rc = -ENOMEM;
         goto err_exit;
@@ -602,7 +780,8 @@ static int tpm_builtin_load_sized_data_f
 
     tsb->size = entry.blobsize;
 
-    if (bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size) != tsb->size) {
+    if (tpm_builtin_bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size, be) !=
+        tsb->size) {
         clear_sized_buffer(tsb);
         fprintf(stderr, "tpm: Error while reading stored data!\n");
         rc = -EIO;
@@ -667,7 +846,8 @@ static int tpm_builtin_save_sized_data_t
     }
 
     if (data_len > 0) {
-        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len) {
+        if (tpm_builtin_bdrv_pwrite(bs, entry.offset, data, data_len, be) !=
+            data_len) {
             rc = -EIO;
         }
     }
@@ -1492,11 +1672,77 @@ static const char *tpm_builtin_create_de
 }
 
 
+/*
+ * Convert a string of hex digits to its binary representation.
+ * The conversion stops once either the maximum size of the binary
+ * array has been reached or an non-hex digit was encountered.
+ */
+static size_t stream_to_bin(const char *stream,
+                            unsigned char *bin, size_t bin_size)
+{
+    size_t c = 0;
+    unsigned char nib = 0;
+
+    while (c < bin_size && stream[c] != 0) {
+        if (stream[c] >= '0' && stream[c] <= '9') {
+            nib |= stream[c] - '0';
+        } else if (stream[c] >= 'A' && stream[c] <= 'F') {
+            nib |= stream[c] - 'A' + 10;
+        } else if (stream[c] >= 'a' && stream[c] <= 'f') {
+            nib |= stream[c] - 'a' + 10;
+        } else {
+            break;
+        }
+
+        if ((c & 1) == 1) {
+            bin[c/2] = nib;
+            nib = 0;
+        } else {
+            nib <<= 4;
+            bin[c/2] = nib;
+        }
+
+        c++;
+    }
+
+    return c;
+}
+
+
+static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
+                                        unsigned char *keyvalue,
+                                        int *keysize)
+{
+    size_t c = 0;
+
+    /* skip over leading '0x' */
+    if (!strncmp(rawkey, "0x", 2)) {
+        rawkey += 2;
+    }
+
+    c = stream_to_bin(rawkey, keyvalue, *keysize);
+
+    if (c == 256/4) {
+        *keysize = 256;
+    } else if (c >= 192/4) {
+        *keysize = 192;
+    } else if (c >= 128/4) {
+        *keysize = 128;
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+
 static TPMBackend *tpm_builtin_create(QemuOpts *opts, const char *id,
                                       const char *model)
 {
     TPMBackend *driver;
     const char *value;
+    unsigned char keyvalue[256/8];
+    int keysize = sizeof(keyvalue);
 
     driver = g_malloc(sizeof(TPMBackend));
     if (!driver) {
@@ -1523,6 +1769,33 @@ static TPMBackend *tpm_builtin_create(Qe
         goto err_exit;
     }
 
+    value = qemu_opt_get(opts, "key");
+    if (value) {
+        if (!strncasecmp(value, "aes-cbc:", 8)) {
+            memset(keyvalue, 0x0, sizeof(keyvalue));
+
+            if (!tpm_builtin_parse_as_hexkey(&value[8], keyvalue, &keysize)) {
+                keysize = 128;
+                strncpy((char *)keyvalue, value, 128/8);
+            }
+
+            if (AES_set_encrypt_key(keyvalue, keysize,
+                                    &enckey.tpm_enc_key) != 0 ||
+                AES_set_decrypt_key(keyvalue, keysize,
+                                    &enckey.tpm_dec_key) != 0) {
+                fprintf(stderr, "tpm: Error setting AES key.\n");
+                goto err_exit;
+            }
+            enckey.enctype = BS_DIR_ENCTYPE_AES_CBC;
+        } else {
+            fprintf(stderr, "tpm: Unknown encryption scheme. Known types are: "
+                            "aes-cbc.\n");
+            goto err_exit;
+        }
+    } else {
+        enckey.enctype = BS_DIR_ENCTYPE_NONE;
+    }
+
     return driver;
 
 err_exit:
Index: qemu-git/qemu-config.c
===================================================================
--- qemu-git.orig/qemu-config.c
+++ qemu-git/qemu-config.c
@@ -522,6 +522,11 @@ static QemuOptsList qemu_tpmdev_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Persistent storage for TPM state",
         },
+        {
+            .name = "key",
+            .type = QEMU_OPT_STRING,
+            .help = "Data encryption key",
+        },
         { /* end of list */ }
     },
 };
@@ -546,6 +551,11 @@ static QemuOptsList qemu_tpm_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Persistent storage for TPM state",
         },
+        {
+            .name = "key",
+            .type = QEMU_OPT_STRING,
+            .help = "Data encryption key",
+        },
         { /* end of list */ }
     },
 };
Index: qemu-git/tpm.c
===================================================================
--- qemu-git.orig/tpm.c
+++ qemu-git/tpm.c
@@ -245,6 +245,7 @@ void tpm_cleanup(void)
 void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
 {
     QemuOpts *opts;
+    char *key;
 
     if (strcmp("none", optarg) != 0) {
         if (*optarg == '?') {
@@ -255,6 +256,15 @@ void tpm_config_parse(QemuOptsList *opts
         if (!opts) {
             exit(1);
         }
+
+        /* if a key is provided, wipe it out so no one can see it with 'ps' */
+        key = strstr(optarg, "key=");
+        if (key) {
+            key += 4;
+            while (key[0] && key[0] != ',') {
+                *key++ = '-';
+            }
+        }
     }
 }
 
Index: qemu-git/qemu-options.hx
===================================================================
--- qemu-git.orig/qemu-options.hx
+++ qemu-git/qemu-options.hx
@@ -1766,8 +1766,9 @@ DEFHEADING(TPM device options:)
 # ifdef CONFIG_TPM
 DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
     "" \
-    "-tpm builtin,path=<path>[,model=<model>]\n" \
+    "-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 model=?    to list available TPM device models\n" \
     "-tpm ?          to list available TPM backend types\n",
     QEMU_ARCH_I386)
@@ -1796,13 +1797,22 @@ Use ? to print all available TPM backend
 qemu -tpmdev ?
 @end example
 
-@item -tpmdev builtin ,id=@var{id}, path=@var{path}
+@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
 
 Creates an instance of the built-in TPM.
 
 @option{path} specifies the path to the QCoW2 image that will store
 the TPM's persistent data. @option{path} is required.
 
+@option{key} specifies the AES key to use to encrypt the TPM's persistent
+data. If encryption is to be used, the key must be provided the first
+time a Qemu VM with attached TPM is started and the same key must subsequently
+be used. The format of the key is the type of encryption to use, i.e.,
+@code{aes-cbc}, followed by a colon and then the actual key. The key can
+be a hex number with optional leading @code{0x}
+and 32, 48 or 64 hex digits for 128, 192 or 256 bit AES keys respectively.
+@option{key} is optional.
+
 To create a built-in TPM use the following two options:
 @example
 -tpmdev builtin,id=tpm0,path=<path_to_qcow2> -device tpm-tis,tpmdev=tpm0
@@ -1810,12 +1820,18 @@ To create a built-in TPM use the followi
 Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
 @code{tpmdev=tpm0} in the device option.
 
+
+To create a built-in TPM whose state is encrypted with a 128 bit AES key
+using AES-CBC encryption scheme supply the following two options:
+@example
+-tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
+@end example
 @end table
 
 The short form of a TPM device option is:
 @table @option
 
-@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}]
+@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
 @findex -tpm
 
 @option{model} specifies the device model. The default device model is a

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 11/14] Experimental support for block migrating TPMs state
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (9 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 12/14] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
                   ` (3 subsequent siblings)
  14 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_blkmig.diff --]
[-- Type: text/plain, Size: 2949 bytes --]

This patch adds (experimental) support for block migration.

In the case of block migration an empty QCoW2 image must be found on
the destination so that early checks on the content and whether it can be
decrytped with the provided key have to be skipped. That empty file needs
to be created by higher layers (i.e., libvirt).

Also, the completion of the block migration has to be delayed until after
the TPM has written the last bytes of its state into the block device so
that we get the latest state on the target as well. Before the change to
savevm.c it could happen that the latest state of the TPM did not make it to
the destination host since the TPM was still processing a command and
changing its state (written into block storage) but the block migration
already had finished. Re-ordering the saving of the live_state to finish
after the 'non live_state' seems to get it right.

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

---
 hw/tpm_builtin.c |    5 +++++
 savevm.c         |   23 ++++++++++++-----------
 2 files changed, 17 insertions(+), 11 deletions(-)

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -488,6 +488,11 @@ static int tpm_builtin_startup_bs(BlockD
 
     if (!tpm_builtin_is_valid_bsdir(dir) ||
         !tpm_builtin_has_valid_content(dir)) {
+        if (incoming_expected) {
+            /* during migration with block migration, we may end
+               up here due to an empty block file */
+            return -ENOKEY;
+        }
         /* if it's encrypted and has something else than null-content,
            we assume to have the wrong key */
         if (bdrv_is_encrypted(bs)) {
Index: qemu-git/savevm.c
===================================================================
--- qemu-git.orig/savevm.c
+++ qemu-git/savevm.c
@@ -1547,17 +1547,6 @@ int qemu_savevm_state_complete(Monitor *
     cpu_synchronize_all_states();
 
     QTAILQ_FOREACH(se, &savevm_handlers, entry) {
-        if (se->save_live_state == NULL)
-            continue;
-
-        /* Section type */
-        qemu_put_byte(f, QEMU_VM_SECTION_END);
-        qemu_put_be32(f, se->section_id);
-
-        se->save_live_state(mon, f, QEMU_VM_SECTION_END, se->opaque);
-    }
-
-    QTAILQ_FOREACH(se, &savevm_handlers, entry) {
         int len;
 
 	if (se->save_state == NULL && se->vmsd == NULL)
@@ -1578,6 +1567,18 @@ int qemu_savevm_state_complete(Monitor *
         vmstate_save(f, se);
     }
 
+    QTAILQ_FOREACH(se, &savevm_handlers, entry) {
+        if (se->save_live_state == NULL) {
+            continue;
+        }
+
+        /* Section type */
+        qemu_put_byte(f, QEMU_VM_SECTION_END);
+        qemu_put_be32(f, se->section_id);
+
+        se->save_live_state(mon, f, QEMU_VM_SECTION_END, se->opaque);
+    }
+
     qemu_put_byte(f, QEMU_VM_EOF);
 
     if (qemu_file_has_error(f))

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 12/14] Support for taking measurements when kernel etc. are passed to Qemu
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (10 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 11/14] Experimental support for block migrating TPMs state Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation Stefan Berger
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_paravirt.diff --]
[-- Type: text/plain, Size: 12690 bytes --]

This patch adds support for hashing the kernel and initrd as well as the
command line parameters in the case that Qemu was provided the -kernel, -initrd
and -apppend command line parameters. The hashes are then passed to SeaBIOS
for logging. Typically SeaBIOS would take those measurements (hashing) but in
the case Qemu gets these command line parameters, Qemu does not see the kernel
file in its unmodified form anymore (it is modified before it is passed
to the firmware interface). Support for measuring multiboot kernel entries is
also added.

This patch relies on the existing firmware mechanism to pass byte arrays
from Qemu to a BIOS, i.e., SeaBIOS. It introduces structures describing the
header and the following content consisting of an array of structures that
hold the measurements and descriptions of the above mentioned items.

Since hashing requires a sha1 algorithm to be available to Qemu, this patch
introduces a dependency on the freebl library for the sha1 algorithm. The
code for accessing the freebl library's sha1 function has been isolated into
its own file and wrapped with the function call qemu_sha1. Attempts to use the
freebl library's SHA1 function directly didn't work due to clashes of
datatypes with matching names defined by freebl and Qemu.

-v8:
  - basing qemu_sha1 on nss lib's HASH_HashBuf() 
-v7:
  - Support for multiboot kernels

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

---
 Makefile.target |    2 -
 configure       |    9 ++++
 hw/fw_cfg.h     |    2 +
 hw/multiboot.c  |    8 ++++
 hw/pc.c         |   10 +++++
 sha1.c          |   19 +++++++++
 sha1.h          |    9 ++++
 tpm.c           |  107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 tpm.h           |   33 +++++++++++++++++
 9 files changed, 198 insertions(+), 1 deletion(-)

Index: qemu-git/hw/pc.c
===================================================================
--- qemu-git.orig/hw/pc.c
+++ qemu-git/hw/pc.c
@@ -679,6 +679,9 @@ static void load_linux(void *fw_cfg,
 	exit(1);
     }
 
+    tpm_measure_start();
+    tpm_measure_file(kernel_filename, TPM_MSR_TYPE_KERNEL, 8);
+
     /* kernel protocol version */
 #if 0
     fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202));
@@ -736,6 +739,9 @@ static void load_linux(void *fw_cfg,
                      (uint8_t*)strdup(kernel_cmdline),
                      strlen(kernel_cmdline)+1);
 
+    tpm_measure_buffer(kernel_cmdline, strlen(kernel_cmdline),
+                       TPM_MSR_TYPE_KERNEL_CMDLINE, 8, NULL, 0);
+
     if (protocol >= 0x202) {
 	stl_p(header+0x228, cmdline_addr);
     } else {
@@ -797,9 +803,13 @@ static void load_linux(void *fw_cfg,
         fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
         fw_cfg_add_bytes(fw_cfg, FW_CFG_INITRD_DATA, initrd_data, initrd_size);
 
+        tpm_measure_buffer(initrd_data, initrd_size, TPM_MSR_TYPE_INITRD, 8,
+                           initrd_filename, strlen(initrd_filename) + 1);
+
 	stl_p(header+0x218, initrd_addr);
 	stl_p(header+0x21c, initrd_size);
     }
+    tpm_measure_end(fw_cfg);
 
     /* load kernel and setup */
     setup_size = header[0x1f1];
Index: qemu-git/tpm.h
===================================================================
--- qemu-git.orig/tpm.h
+++ qemu-git/tpm.h
@@ -1,6 +1,9 @@
 #ifndef _HW_TPM_CONFIG_H
 #define _HW_TPM_CONFIG_H
 
+#include "sysemu.h"
+#include "hw/fw_cfg.h"
+
 struct TPMState;
 typedef struct TPMState TPMState;
 
@@ -108,6 +111,36 @@ void do_info_tpm(Monitor *mon);
 void tpm_display_backend_drivers(FILE *out);
 const TPMDriverOps *tpm_get_backend_driver(const char *id);
 
+typedef enum TPMMeasureType {
+    TPM_MSR_TYPE_KERNEL_CMDLINE = 0x1105,
+    TPM_MSR_TYPE_KERNEL = 0x1205,
+    TPM_MSR_TYPE_INITRD = 0x1305,
+    TPM_MSR_TYPE_MODULE_CMDLINE = 0x1405,
+    TPM_MSR_TYPE_MODULE = 0x1505,
+} TPMMeasureType;
+
+typedef struct TPMMsrHdr {
+    uint16_t rev;
+    uint32_t totlen;
+    uint16_t numTPMMsrEntries;
+} __attribute__((packed)) TPMMsrHdr;
+
+typedef struct TPMMsrEntry {
+    uint32_t len;
+    uint32_t pcrindex;
+    uint32_t type;
+    uint8_t  digest[20];
+    uint32_t eventdatasize;
+    uint32_t event;
+} __attribute__((packed)) TPMMsrEntry;
+
+void tpm_measure_start(void);
+void tpm_measure_end(FWCfgState *s);
+void tpm_measure_file(const char *, TPMMeasureType type, uint8_t pcrindex);
+void tpm_measure_buffer(const void *buffer, long length,
+                        TPMMeasureType type, uint8_t pcrindex,
+                        const void *data, uint32_t data_len);
+
 extern TPMDriverOps tpm_builtin;
 
 #endif /* _HW_TPM_CONFIG_H */
Index: qemu-git/tpm.c
===================================================================
--- qemu-git.orig/tpm.c
+++ qemu-git/tpm.c
@@ -14,6 +14,9 @@
 #include "tpm.h"
 #include "monitor.h"
 #include "qerror.h"
+#include "sha1.h"
+#include "hw/loader.h"
+#include "bswap.h"
 
 
 #ifdef CONFIG_TPM
@@ -268,6 +271,88 @@ void tpm_config_parse(QemuOptsList *opts
     }
 }
 
+static TPMMsrHdr *tpm_measurements;
+
+void tpm_measure_start(void)
+{
+    if (!tpm_measurements) {
+        tpm_measurements = g_malloc0(sizeof(TPMMsrHdr));
+        tpm_measurements->rev = 1;
+        tpm_measurements->totlen = sizeof(TPMMsrHdr);
+    }
+}
+
+void tpm_measure_end(FWCfgState *s)
+{
+    uint32_t totlen = tpm_measurements->totlen;
+    /* fix endianess */
+    tpm_measurements->rev    = cpu_to_le16(tpm_measurements->rev);
+    tpm_measurements->totlen = cpu_to_le32(totlen);
+    tpm_measurements->numTPMMsrEntries =
+                               cpu_to_le16(tpm_measurements->numTPMMsrEntries);
+
+    fw_cfg_add_i32(s, FW_CFG_TPM_MEASURE_SIZE, totlen);
+    fw_cfg_add_bytes(s, FW_CFG_TPM_MEASURE_DATA,
+                     (unsigned char *)tpm_measurements, totlen);
+}
+
+static void tpm_measure_add_hash(unsigned char digest[20], TPMMeasureType type,
+                                 uint8_t pcrindex,
+                                 const void *data, uint32_t len)
+{
+    TPMMsrEntry *entry;
+
+    if (tpm_measurements) {
+        uint32_t entry_len = sizeof(TPMMsrEntry) + len;
+        tpm_measurements = g_realloc(tpm_measurements,
+                                     tpm_measurements->totlen +
+                                     entry_len);
+        if (tpm_measurements) {
+            entry = (void *)tpm_measurements + tpm_measurements->totlen;
+
+            tpm_measurements->totlen += entry_len;
+            tpm_measurements->numTPMMsrEntries++;
+
+            entry->len           = cpu_to_le32(entry_len);
+            entry->pcrindex      = cpu_to_le32(pcrindex);
+            entry->type          = cpu_to_le32(type);
+            memcpy(entry->digest, digest, sizeof(entry->digest));
+            entry->eventdatasize = cpu_to_le32(len);
+            if (len) {
+                memcpy(&entry->event, data, len);
+            }
+        }
+    }
+}
+
+void tpm_measure_buffer(const void *buffer, long len,
+                        TPMMeasureType type, uint8_t pcrindex,
+                        const void *data, uint32_t data_len)
+{
+    unsigned char hash[20];
+
+    qemu_sha1(hash, buffer, len);
+
+    tpm_measure_add_hash(hash, type, pcrindex, data, data_len);
+}
+
+void tpm_measure_file(const char *filename, TPMMeasureType type,
+                      uint8_t pcrindex)
+{
+    int len = get_image_size(filename);
+
+    if (len > 0) {
+        uint8_t *buffer = g_malloc(len);
+        if (buffer) {
+            if (load_image(filename, buffer) == len) {
+                tpm_measure_buffer(buffer, len, type, pcrindex,
+                                   filename, strlen(filename) + 1);
+            }
+            g_free(buffer);
+        }
+    }
+}
+
 # else /* TARGET_I386 || TARGET_X86_64 */
 
 void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
@@ -289,4 +374,26 @@ void do_info_tpm(Monitor *mon)
 }
 
 # endif
+
+#else /* ! CONFIG_TPM */
+
+void tpm_measure_start(void)
+{
+}
+
+void tpm_measure_end(FWCfgState *s)
+{
+}
+
+void tpm_measure_buffer(const void *buffer, long len,
+                        TPMMeasureType type, uint8_t pcrindex,
+                        const void *data, uint32_t data_len)
+{
+}
+
+void tpm_measure_file(const char *filename, TPMMeasureType type,
+                      uint8_t pcrindex)
+{
+}
+
 #endif /* CONFIG_TPM */
Index: qemu-git/hw/fw_cfg.h
===================================================================
--- qemu-git.orig/hw/fw_cfg.h
+++ qemu-git/hw/fw_cfg.h
@@ -27,6 +27,8 @@
 #define FW_CFG_SETUP_SIZE       0x17
 #define FW_CFG_SETUP_DATA       0x18
 #define FW_CFG_FILE_DIR         0x19
+#define FW_CFG_TPM_MEASURE_SIZE 0x1a
+#define FW_CFG_TPM_MEASURE_DATA 0x1b
 
 #define FW_CFG_FILE_FIRST       0x20
 #define FW_CFG_FILE_SLOTS       0x10
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
+obj-i386-$(CONFIG_TPM) += tpm_tis.o sha1.o
 obj-i386-$(CONFIG_TPM_BUILTIN) += tpm_builtin.o
 
 ifdef CONFIG_TPM_BUILTIN
Index: qemu-git/sha1.c
===================================================================
--- /dev/null
+++ qemu-git/sha1.c
@@ -0,0 +1,19 @@
+/*
+ * SHA1 Freebl wrapper
+ *
+ * Copyright (C) 2011 IBM Corporation
+ * Copyright (C) 2011 Stefan Berger
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "sha1.h"
+
+#include <sechash.h>
+
+int qemu_sha1(unsigned char hash[20], const unsigned char *data, uint32_t len)
+{
+    return HASH_HashBuf(HASH_AlgSHA1, hash, (unsigned char *)data, len);
+}
Index: qemu-git/sha1.h
===================================================================
--- /dev/null
+++ qemu-git/sha1.h
@@ -0,0 +1,9 @@
+#ifndef __SHA1_H
+#define __SHA1_H
+
+#include <stdint.h>
+
+int qemu_sha1(unsigned char hash[20], const unsigned char *data,
+              uint32_t length);
+
+#endif /* __SHA1_H */
Index: qemu-git/configure
===================================================================
--- qemu-git.orig/configure
+++ qemu-git/configure
@@ -2577,6 +2577,15 @@ fi
 # libtpms probe
 
 if test "$tpm" = "yes" ; then
+  if $pkg_config --atleast-version=3.12.8 nss >/dev/null 2>&1 ; then
+    tpmsupport_cflags=$($pkg_config --cflags nss 2>/dev/null)
+    tpmsupport_libs="-lnss3"
+    QEMU_CFLAGS="$QEMU_CFLAGS $tpmsupport_cflags"
+    LIBS="$LIBS $tpmsupport_libs"
+  else
+    feature_not_found "nss"
+  fi
+
   cat > $TMPC <<EOF
 #include <libtpms/tpm_library.h>
 int main(void) { return (int)TPMLIB_GetVersion(); }
Index: qemu-git/hw/multiboot.c
===================================================================
--- qemu-git.orig/hw/multiboot.c
+++ qemu-git/hw/multiboot.c
@@ -28,6 +28,7 @@
 #include "loader.h"
 #include "elf.h"
 #include "sysemu.h"
+#include "tpm.h"
 
 /* Show multiboot debug output */
 //#define DEBUG_MULTIBOOT
@@ -100,6 +101,8 @@ static uint32_t mb_add_cmdline(Multiboot
     target_phys_addr_t p = s->offset_cmdlines;
     char *b = (char *)s->mb_buf + p;
 
+    tpm_measure_buffer(cmdline, strlen(cmdline),
+                       TPM_MSR_TYPE_MODULE_CMDLINE, 8, NULL, 0);
     get_opt_value(b, strlen(cmdline) + 1, cmdline);
     s->offset_cmdlines += strlen(b) + 1;
     return s->mb_buf_phys + p;
@@ -283,6 +286,7 @@ int load_multiboot(void *fw_cfg,
             mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_mod_length + mbs.mb_buf_size);
             mbs.mb_buf = g_realloc(mbs.mb_buf, mbs.mb_buf_size);
 
+            tpm_measure_file(initrd_filename, TPM_MSR_TYPE_MODULE, 8);
             load_image(initrd_filename, (unsigned char *)mbs.mb_buf + offs);
             mb_add_mod(&mbs, mbs.mb_buf_phys + offs,
                        mbs.mb_buf_phys + offs + mb_mod_length, c);
@@ -299,6 +303,8 @@ int load_multiboot(void *fw_cfg,
     snprintf(kcmdline, sizeof(kcmdline), "%s %s",
              kernel_filename, kernel_cmdline);
     stl_p(bootinfo + MBI_CMDLINE, mb_add_cmdline(&mbs, kcmdline));
+    tpm_measure_buffer(kcmdline, strlen(kcmdline),
+                       TPM_MSR_TYPE_KERNEL_CMDLINE, 8, NULL, 0);
 
     stl_p(bootinfo + MBI_MODS_ADDR,  mbs.mb_buf_phys + mbs.offset_mbinfo);
     stl_p(bootinfo + MBI_MODS_COUNT, mbs.mb_mods_count); /* mods_count */
@@ -339,5 +345,7 @@ int load_multiboot(void *fw_cfg,
     option_rom[nb_option_roms].bootindex = 0;
     nb_option_roms++;
 
+    tpm_measure_end(fw_cfg);
+
     return 1; /* yes, we are multiboot */
 }

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (11 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 12/14] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-09-01 17:40   ` Michael S. Tsirkin
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state Stefan Berger
  2011-09-01 18:12 ` [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Michael S. Tsirkin
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_be_null.diff --]
[-- Type: text/plain, Size: 12611 bytes --]

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 <stefanb@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

^ permalink raw reply	[flat|nested] 75+ messages in thread

* [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (12 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation Stefan Berger
@ 2011-08-31 14:36 ` Stefan Berger
  2011-09-01 18:10   ` Michael S. Tsirkin
  2011-09-01 18:12 ` [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Michael S. Tsirkin
  14 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-08-31 14:36 UTC (permalink / raw)
  To: stefanb, qemu-devel
  Cc: chrisw, anbang.ruan, rrelyea, alevy, andreas.niederl, serge

[-- Attachment #1: qemu_tpm_initstate.diff --]
[-- Type: text/plain, Size: 10106 bytes --]

This patch adds a -tpm ...,initstate=...,... command line option to the
TPM's existing options and enables the TPM to be initialized with an
existing state blob. This in turn allows us to simulate TPM manufacturing
and equip the TPM with an endorsement key, certificates and initialize its
NVRAM areas etc.. This step is typically done during manufacturng of the TPM
and/or the (physical) machine.

The initial state can be passed either as file or via a file descriptor. The
encoding of the state can either be binary or in form of a base64-encoded
blob surrounded by tags indicating the start and end.

The intial state can be produced through a yet-to-be-published tpm-authoring
tool.

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

---
 hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 qemu-config.c    |   12 +++++
 qemu-options.hx  |   40 ++++++++++++++++-
 3 files changed, 172 insertions(+), 3 deletions(-)

Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- qemu-git.orig/hw/tpm_builtin.c
+++ qemu-git/hw/tpm_builtin.c
@@ -170,9 +170,16 @@ static struct enckey {
     AES_KEY tpm_dec_key;
 } enckey;
 
+static int tpm_initstatefd = -1;
+static bool tpm_initstate_bin;
+
 static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
                                                enum BSEntryType be,
                                                TPMSizedBuffer *tsb);
+static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
+                                                uint32_t *length,
+                                                size_t tpm_number,
+                                                const char *name);
 
 
 
@@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load
         *length = permanent_state.size;
 
         if (*length == 0) {
-            rc = TPM_RETRY;
+            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
         } else {
             /* keep a copy of the last permanent state */
             rc = TPM_Malloc(data, *length);
@@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys
 }
 
 
+static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
+                                                uint32_t *length,
+                                                size_t tpm_number,
+                                                const char *name)
+{
+    TPM_RESULT rc = TPM_RETRY;
+    uint32_t allocated = 0;
+    int len, flags;
+    unsigned char buf[1024];
+    unsigned char *result = NULL;
+    size_t result_len;
+
+    if (tpm_initstatefd >= 0) {
+        *data = NULL;
+        *length = 0;
+
+        flags = fcntl(tpm_initstatefd, F_GETFL);
+        if (flags < 0 ||
+            fcntl(tpm_initstatefd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
+            return TPM_FAIL;
+        }
+
+        while (TRUE) {
+            len = read(tpm_initstatefd, buf, sizeof(buf));
+
+            if (len > 0) {
+                if (len > allocated - *length) {
+                    allocated = *length + len + 1024;
+                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
+                        goto err_exit;
+                    }
+                }
+                memcpy(&(*data)[*length], buf, len);
+                *length += len;
+                (*data)[*length] = 0;
+            } else if (len == 0) {
+                rc = TPM_SUCCESS;
+                break;
+            } else if (len < 0) {
+                if (errno == EINTR) {
+                    continue;
+                }
+                goto err_exit;
+            }
+        }
+
+        if (*data == NULL) {
+            /* nothing read */
+            rc = TPM_FAIL;
+            goto err_exit;
+        }
+
+        if (!tpm_initstate_bin) {
+            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
+                                  &result, &result_len) != TPM_SUCCESS) {
+                goto err_exit;
+            }
+            TPM_Free(*data);
+            *data = result;
+            *length = result_len;
+            result = NULL;
+        }
+        /* sanity check for the size of the blob */
+        if (*length > tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
+            goto err_exit;
+        }
+        /* have it written into the BlockStorage */
+        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);
+        if (rc != TPM_SUCCESS) {
+            goto err_exit;
+        }
+    }
+
+norm_exit:
+    close(tpm_initstatefd);
+    tpm_initstatefd = -1;
+
+    return rc;
+
+err_exit:
+    TPM_Free(*data);
+    *data = NULL;
+    *length = 0;
+    TPM_Free(result);
+
+    goto norm_exit;
+}
+
 /*****************************************************************/
 
 
@@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe
     const char *value;
     unsigned char keyvalue[256/8];
     int keysize = sizeof(keyvalue);
+    unsigned int offset;
 
     driver = g_malloc(sizeof(TPMBackend));
     if (!driver) {
@@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe
         enckey.enctype = BS_DIR_ENCTYPE_NONE;
     }
 
+    value = qemu_opt_get(opts, "initstate");
+    if (value) {
+        offset = 0;
+
+        if (!strncmp(value, "bin:", 4)) {
+            tpm_initstate_bin = true;
+            offset = 4;
+        } else if (!strncmp(value, "base64:", 7)) {
+            tpm_initstate_bin = false;
+            offset = 7;
+        }
+
+        if (sscanf(&value[offset], "fd:%d", &tpm_initstatefd) != 1) {
+            tpm_initstatefd = open(&value[offset], O_RDONLY);
+            if (tpm_initstatefd < 0) {
+                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
+                        value);
+                goto err_exit;
+            }
+        }
+    }
+
     return driver;
 
 err_exit:
@@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke
     g_free(driver->id);
     g_free(driver->model);
     g_free(driver);
+
+    close(tpm_initstatefd);
+    tpm_initstatefd = -1;
 }
 
 
Index: qemu-git/qemu-config.c
===================================================================
--- qemu-git.orig/qemu-config.c
+++ qemu-git/qemu-config.c
@@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Data encryption key",
         },
+        {
+            .name = "initstate",
+            .type = QEMU_OPT_STRING,
+            .help = "File or file descriptor for reading initial TPM state "
+                    "from",
+        },
         { /* end of list */ }
     },
 };
@@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = {
             .type = QEMU_OPT_STRING,
             .help = "Data encryption key",
         },
+        {
+            .name = "initstate",
+            .type = QEMU_OPT_STRING,
+            .help = "File or file descriptor for reading initial TPM state "
+                    "from",
+        },
         { /* end of list */ }
     },
 };
Index: qemu-git/qemu-options.hx
===================================================================
--- qemu-git.orig/qemu-options.hx
+++ qemu-git/qemu-options.hx
@@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:)
 DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
     "" \
     "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
+    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
     "                enable a builtin TPM with state in file in path\n" \
     "                and encrypt the TPM's state with the given AES key\n" \
+    "                initstate= path to initial state of TPM; default is base64\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" \
@@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend
 qemu -tpmdev ?
 @end example
 
-@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
+@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
 
 Creates an instance of the built-in TPM.
 
@@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t
 -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
 @end example
 
+@option{initstate} specifies the path to a file containing the initial
+state of the TPM. It can be used to provide the TPM with an EK and certificates
+for the EK, TPM and Platform. Since the file contains binary data that
+have to conform to the TPM's layout of data, it must have been created using
+an approriate authoring tool.
+
+The initstate option allows to provide a binary state blob or one that is
+encode in base 64. The base64-encode state blob must have the format
+
+@example
+-----BEGIN INITSTATE-----
+<base 64 encoded state>
+-----END INITSTATE-----
+@end example
+
+The initstate option is only effective when Qemu is started with blank
+state.
+
+The initstate option supports several formats:
+
+@table @option
+ @item  [base64:]<path_to_blob>
+ Provide the path to the TPM's initial state blob in base64 format.
+ @item  bin:<path to blob>
+ Provide the path to the TPM's initial state blob in binary format.
+ @item  [base64:]fd:<fd>
+ Provide the base64 formatted initial state via a file descriptor to read from.
+ @item  bin:fd:<fd>
+ Provide the binary initial state via a file descriptor to read from.
+@end table
+
+@option{initstate} is optional.
+
+
 @item -tpmdev null
 
 Creates an instance of a TPM null driver that responds to every command
@@ -1840,7 +1876,7 @@ with a fault message.
 The short form of a TPM device option is:
 @table @option
 
-@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
+@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}] [,initstate=@var{path}]
 @findex -tpm
 
 @option{model} specifies the device model. The default device model is a

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
@ 2011-09-01 17:14   ` Michael S. Tsirkin
  2011-09-02  1:01     ` Stefan Berger
  2011-09-01 18:14   ` Michael S. Tsirkin
  1 sibling, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:14 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:52AM -0400, Stefan Berger wrote:
> This patch adds support for TPM command line options.
> The command line supported here (considering the libtpms based
> backend) are
> 
> ./qemu-... -tpm builtin,path=<path to blockstorage file>
> 
> and
> 
> ./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
>            -device tpm-tis,tpmdev=<id>

do we really need both?

> and
> 
> ./qemu-... -tpmdev ?
> 
> where the latter works similar to -soundhw ? and shows a list of
> available TPM backends ('builtin').
> 
> To show the available TPM models do:
> 
> ./qemu-... -tpm model=?

Can we live with -tpmdev for backend and plain device_add for frontend?
Frontend would be connected to backend using a tpmdev matching the id
of the frontend...

> In case of -tpm, 'type' (above 'builtin') and 'model' are interpreted in tpm.c.
> In case of -tpmdev 'type' and 'id' are interpreted in tpm.c
> Using the type parameter, the backend is chosen, i.e., 'builtin' for the
> libtpms-based builtin TPM. The interpretation of the other parameters along
> with determining whether enough parameters were provided is pushed into
> the backend driver, which needs to implement the interface function
> 'create' and return a TPMDriver structure if the VM can be started or 'NULL'
> if not enough or bad parameters were provided.
> 
> Since SeaBIOS will now use 128kb for ACPI tables the amount of reserved
> memory for ACPI tables needs to be increased -- increasing it to 128kb.

Increasing from which value to which?

> Monitor support for 'info tpm' has been added. It for example prints the
> following:
> 
> TPM devices:
>   builtin: model=tpm-tis,id=tpm0

This mixes frontend and backend properties.

> 
> v8:
>  - adjusting formatting of backend drivers output to accomodate better
>    formatting of 'passthrough' backend output
> 
> v6:
>  - use #idef CONFIG_TPM to surround TPM calls
>  - use QLIST_FOREACH_SAFE rather than QLIST_FOREACH in tpm_cleanup
>  - commented backend ops in tpm.h
>  - moving to IRQ 5 (11 collided with network cards)
> 
> v5:
>  - fixing typo reported by Serge Hallyn
>  - Adapting code to split command line parameters supporting 
>    -tpmdev ... -device tpm-tis,tpmdev=...
>  - moved code out of arch_init.c|h into tpm.c|h
>  - increasing reserved memory for ACPI tables to 128kb (from 64kb)
>  - the backend interface has a create() function for interpreting the command
>    line parameters and returning a TPMDevice structure; previoulsy
>    this function was called handle_options()
>  - the backend interface has a destroy() function for cleaning up after
>    the create() function was called
>  - added support for 'info tpm' in monitor
> 
> v4:
>  - coding style fixes
> 
> v3:
>  - added hw/tpm_tis.h to this patch so Qemu compiles at this stage
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> 
> ---
>  Makefile.target |    1 
>  hmp-commands.hx |    2 
>  hw/pc.c         |    7 +
>  hw/tpm_tis.h    |   75 +++++++++++++++
>  monitor.c       |   10 ++
>  qemu-config.c   |   46 +++++++++
>  qemu-options.hx |   80 ++++++++++++++++
>  tpm.c           |  279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  tpm.h           |  112 ++++++++++++++++++++++
>  vl.c            |   18 +++
>  10 files changed, 629 insertions(+), 1 deletion(-)
> 
> Index: qemu-git/qemu-options.hx
> ===================================================================
> --- qemu-git.orig/qemu-options.hx
> +++ qemu-git/qemu-options.hx
> @@ -1760,6 +1760,86 @@ ETEXI
>  
>  DEFHEADING()
>  
> +DEFHEADING(TPM device options:)
> +
> +#ifndef _WIN32
> +# ifdef CONFIG_TPM
> +DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
> +    "" \
> +    "-tpm builtin,path=<path>[,model=<model>]\n" \
> +    "                enable a builtin TPM with state in file in path\n" \
> +    "-tpm model=?    to list available TPM device models\n" \
> +    "-tpm ?          to list available TPM backend types\n",
> +    QEMU_ARCH_I386)
> +DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
> +    "-tpmdev [builtin],id=str[,option][,option][,...]\n",
> +    QEMU_ARCH_I386)
> +# endif
> +#endif
> +STEXI
> +
> +The general form of a TPM device option is:
> +@table @option
> +
> +@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
> +@findex -tpmdev
> +Backend type must be:
> +@option{builtin}.
> +
> +The specific backend type will determine the applicable options.
> +The @code{-tpmdev} options requires a @code{-device} option.
> +
> +Options to each backend are described below.
> +
> +Use ? to print all available TPM backend types.
> +@example
> +qemu -tpmdev ?
> +@end example
> +
> +@item -tpmdev builtin ,id=@var{id}, path=@var{path}
> +
> +Creates an instance of the built-in TPM.
> +
> +@option{path} specifies the path to the QCoW2 image that will store
> +the TPM's persistent data. @option{path} is required.
> +
> +To create a built-in TPM use the following two options:
> +@example
> +-tpmdev builtin,id=tpm0,path=<path_to_qcow2> -device tpm-tis,tpmdev=tpm0
> +@end example
> +Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
> +@end table
> +
> +The short form of a TPM device option is:
> +@table @option
> +
> +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}]
> +@findex -tpm
> +
> +@option{model} specifies the device model. The default device model is a
> +@code{tpm-tis} device model. @code{model} is optional.
> +
> +Use ? to print all available TPM models.
> +@example
> +qemu -tpm model=?
> +@end example
> +
> +The other options have the same meaning as explained above.
> +
> +To create a built-in TPM use the following option:
> +@example
> +-tpm builtin, path=<path_to_qcow2>
> +@end example
> +
> +@end table
> +
> +ETEXI
> +
> +
> +DEFHEADING()
> +
>  DEFHEADING(Linux/Multiboot boot specific:)
>  STEXI
>  
> Index: qemu-git/vl.c
> ===================================================================
> --- qemu-git.orig/vl.c
> +++ qemu-git/vl.c
> @@ -137,6 +137,7 @@ int main(int argc, char **argv)
>  #include "block.h"
>  #include "blockdev.h"
>  #include "block-migration.h"
> +#include "tpm.h"
>  #include "dma.h"
>  #include "audio/audio.h"
>  #include "migration.h"
> @@ -2498,6 +2499,14 @@ int main(int argc, char **argv, char **e
>                  ram_size = value;
>                  break;
>              }
> +#ifdef CONFIG_TPM
> +            case QEMU_OPTION_tpm:
> +                tpm_config_parse(qemu_find_opts("tpm"), optarg);
> +                break;
> +            case QEMU_OPTION_tpmdev:
> +                tpm_config_parse(qemu_find_opts("tpmdev"), optarg);
> +                break;
> +#endif
>              case QEMU_OPTION_mempath:
>                  mem_path = optarg;
>                  break;
> @@ -3149,6 +3158,12 @@ int main(int argc, char **argv, char **e
>          exit(1);
>      }
>  
> +#ifdef CONFIG_TPM
> +    if (tpm_init() < 0) {
> +        exit(1);
> +    }
> +#endif
> +
>      /* init the bluetooth world */
>      if (foreach_device_config(DEV_BT, bt_parse))
>          exit(1);
> @@ -3394,6 +3409,9 @@ int main(int argc, char **argv, char **e
>      quit_timers();
>      net_cleanup();
>      res_free();
> +#ifdef CONFIG_TPM
> +    tpm_cleanup();
> +#endif
>  
>      return 0;
>  }
> Index: qemu-git/qemu-config.c
> ===================================================================
> --- qemu-git.orig/qemu-config.c
> +++ qemu-git/qemu-config.c
> @@ -507,6 +507,50 @@ QemuOptsList qemu_boot_opts = {
>      },
>  };
>  
> +static QemuOptsList qemu_tpmdev_opts = {
> +    .name = "tpmdev",
> +    .implied_opt_name = "type",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head),
> +    .desc = {
> +        {
> +            .name = "type",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Type of TPM backend",
> +        },
> +        {
> +            .name = "path",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Persistent storage for TPM state",
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
> +static QemuOptsList qemu_tpm_opts = {
> +    .name = "tpm",
> +    .implied_opt_name = "type",
> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpm_opts.head),
> +    .desc = {
> +        {
> +            .name = "type",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Type of TPM backend",
> +        },
> +        {
> +            .name = "model",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Model of TPM frontend",
> +        },
> +        {
> +            .name = "path",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Persistent storage for TPM state",
> +        },
> +        { /* end of list */ }
> +    },
> +};
> +
> +
>  static QemuOptsList *vm_config_groups[32] = {
>      &qemu_drive_opts,
>      &qemu_chardev_opts,
> @@ -523,6 +567,8 @@ static QemuOptsList *vm_config_groups[32
>      &qemu_option_rom_opts,
>      &qemu_machine_opts,
>      &qemu_boot_opts,
> +    &qemu_tpmdev_opts,
> +    &qemu_tpm_opts,
>      NULL,
>  };
>  
> Index: qemu-git/hw/tpm_tis.h
> ===================================================================
> --- /dev/null
> +++ qemu-git/hw/tpm_tis.h
> @@ -0,0 +1,75 @@
> +/*
> + * tpm_tis.h - include file for tpm_tis.c
> + *
> + * Copyright (C) 2006,2010,2011 IBM Corporation
> + *
> + * Author: Stefan Berger <stefanb@us.ibm.com>
> + *         David Safford <safford@us.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + */
> +#ifndef _HW_TPM_TIS_H
> +#define _HW_TPM_TIS_H
> +
> +#include "isa.h"
> +#include "block_int.h"
> +#include "qemu-thread.h"
> +
> +#include <stdint.h>
> +
> +#define TIS_ADDR_BASE       0xFED40000
> +
> +#define NUM_LOCALITIES      5     /* per spec */
> +#define NO_LOCALITY         0xff

Please use consistent prefixes to avoid namespace
pollution. E.g. tpm_tis_ for stuff in tpm_tis.h, etc.


> +
> +#define IS_VALID_LOCTY(x)   ((x) < NUM_LOCALITIES)
> +
> +
> +#define TPM_TIS_IRQ         5
> +
> +#define TIS_TPM_BUFFER_MAX  4096
> +
> +
> +typedef struct TPMSizedBuffer {
> +    uint32_t size;
> +    uint8_t  *buffer;
> +} TPMSizedBuffer;
> +
> +
> +enum tis_state {
> +    STATE_IDLE = 0,
> +    STATE_READY,
> +    STATE_COMPLETION,
> +    STATE_EXECUTION,
> +    STATE_RECEPTION,
> +};
> +
> +
> +void tis_reset_for_snapshot_resume(TPMState *s);
> +
> +
> +/* utility functions */
> +
> +static inline uint16_t tis_get_size_from_buffer(const TPMSizedBuffer *sb)
> +{
> +    return (sb->buffer[4] << 8) + sb->buffer[5];
> +}
> +
> +static inline void dumpBuffer(FILE *stream,
> +                              unsigned char *buffer, unsigned int len)
> +{
> +    int i;
> +
> +    for (i = 0; i < len; i++) {
> +        if (i && !(i % 16)) {
> +            fprintf(stream, "\n");
> +        }
> +        fprintf(stream, "%.2X ", buffer[i]);
> +    }
> +    fprintf(stream, "\n");
> +}
> +
> +#endif /* _HW_TPM_TIS_H */

> Index: qemu-git/tpm.c
> ===================================================================
> --- /dev/null
> +++ qemu-git/tpm.c
> @@ -0,0 +1,279 @@
> +/*
> + * TPM configuraion
> + *
> + * Copyright (C) 2011 IBM Corporation
> + * Copyright (C) 2011 Stefan Berger
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2.  See
> + * the COPYING file in the top-level directory.
> + *
> + * Based on net.c
> + */
> +#include "config.h"
> +
> +#include "tpm.h"
> +#include "monitor.h"
> +#include "qerror.h"
> +
> +
> +#ifdef CONFIG_TPM
> +
> +#if defined(TARGET_I386) || defined(TARGET_X86_64)
> +
> +static const TPMDriverOps *bes[] = {
> +    NULL,
> +};
> +
> +
> +static const char *tpm_models[] = {
> +    TPM_DEFAULT_DEVICE_MODEL,
> +    NULL,
> +};
> +
> +
> +static QLIST_HEAD(, TPMBackend) tpm_backends =
> +    QLIST_HEAD_INITIALIZER(tpm_backends);
> +
> +
> +const TPMDriverOps *tpm_get_backend_driver(const char *id)
> +{
> +    int i;
> +
> +    for (i = 0; bes[i] != NULL; i++) {
> +        if (!strcmp(bes[i]->id, id)) {
> +            break;
> +        }
> +    }
> +
> +    return bes[i];
> +}
> +
> +
> +static void tpm_display_models(FILE *out)
> +{
> +    int i;
> +
> +    fprintf(stderr, "qemu: Supported TPM models: ");
> +    for (i = 0 ; tpm_models[i]; i++) {
> +        fprintf(stderr, "%s%c", tpm_models[i], tpm_models[i+1] ? ',' : '\n');
> +    }
> +}
> +
> +
> +static int tpm_check_model(const char *model)
> +{
> +    int i;
> +
> +    for (i = 0 ; tpm_models[i]; i++) {
> +        if (strcmp(tpm_models[i], model) == 0) {
> +            return 1;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +
> +void tpm_display_backend_drivers(FILE *out)
> +{
> +    int i;
> +
> +    fprintf(out, "Supported TPM types (choose only one):\n");
> +
> +    for (i = 0; bes[i] != NULL; i++) {
> +        fprintf(out, "%12s   %s",
> +                bes[i]->id, bes[i]->desc());
> +        fprintf(out, "\n");
> +    }
> +    fprintf(out, "\n");
> +}
> +
> +
> +TPMBackend *qemu_find_tpm(const char *id)
> +{
> +    TPMBackend *drv;
> +
> +    QLIST_FOREACH(drv, &tpm_backends, list) {
> +        if (!strcmp(drv->id, id)) {
> +            return drv;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +
> +void do_info_tpm(Monitor *mon)
> +{
> +    TPMBackend *drv;
> +    const char *model;
> +
> +    monitor_printf(mon, "TPM devices:\n");
> +
> +    QLIST_FOREACH(drv, &tpm_backends, list) {
> +        model = drv->model ? drv->model : TPM_DEFAULT_DEVICE_MODEL;
> +        monitor_printf(mon, "  %s: model=%s,id=%s\n",
> +                       drv->ops->id, model, drv->id);
> +    }
> +}
> +
> +/*
> + * Create those TPMs that were created with -tpm rather than -tpmdev.
> + * The ones created with -tpm have a 'model' name.
> + */
> +void qemu_create_tpm(void)
> +{
> +    TPMBackend *drv;
> +
> +    QLIST_FOREACH(drv, &tpm_backends, list) {
> +        if (drv->model) {
> +            if (strcmp(drv->model, TPM_DEFAULT_DEVICE_MODEL) == 0) {
> +                isa_create_simple(drv->model);
> +            }
> +        }
> +    }
> +}
> +
> +
> +static int configure_tpm(QemuOpts *opts, int is_tpmdev)
> +{
> +    const char *value;
> +    const char *id = TPM_DEFAULT_DEVICE_ID;
> +    const char *model =  NULL;
> +    const TPMDriverOps *be;
> +    TPMBackend *drv;
> +
> +    if (!QLIST_EMPTY(&tpm_backends)) {
> +        fprintf(stderr, "Only one TPM is allowed.\n");
> +        return 1;
> +    }
> +
> +    if (is_tpmdev) {
> +        id = qemu_opts_id(opts);
> +        if (id == NULL) {
> +            qerror_report(QERR_MISSING_PARAMETER, "id");
> +            return 1;
> +        }
> +    } else {
> +        model = qemu_opt_get(opts, "model");
> +        if (model) {
> +            if (strcmp(model, "?") == 0) {
> +                tpm_display_models(stdout);
> +                return 1;
> +            }
> +            if (!tpm_check_model(model)) {
> +                qerror_report(QERR_INVALID_PARAMETER_VALUE, "model",
> +                              "a tpm model");
> +                tpm_display_models(stderr);
> +                return 1;
> +            }
> +        } else {
> +            model = TPM_DEFAULT_DEVICE_MODEL;
> +        }
> +    }
> +
> +    value = qemu_opt_get(opts, "type");
> +    if (!value) {
> +        qerror_report(QERR_MISSING_PARAMETER, "type");
> +        tpm_display_backend_drivers(stderr);
> +        return 1;
> +    }
> +
> +    be = tpm_get_backend_driver(value);
> +    if (be == NULL) {
> +        qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
> +                      "a tpm backend type");
> +        tpm_display_backend_drivers(stderr);
> +        return 1;
> +    }
> +
> +    assert((is_tpmdev && model == NULL) || (!is_tpmdev && model != NULL));

Why isn't this using qdev for parameter passing?

> +
> +    drv = be->create(opts, id, model);
> +    if (!drv) {
> +        return 1;
> +    }
> +
> +    QLIST_INSERT_HEAD(&tpm_backends, drv, list);
> +
> +    return 0;
> +}
> +
> +
> +static int tpm_init_tpmdev(QemuOpts *opts, void *dummy)
> +{
> +    return configure_tpm(opts, 1);
> +}
> +
> +
> +static int tpm_init_tpm(QemuOpts *opts, void *dummy)
> +{
> +    return configure_tpm(opts, 0);
> +}
> +
> +
> +int tpm_init(void)
> +{
> +    if (qemu_opts_foreach(qemu_find_opts("tpmdev"),
> +                          tpm_init_tpmdev, NULL, 1) != 0) {
> +        return -1;
> +    }
> +
> +    if (qemu_opts_foreach(qemu_find_opts("tpm"),
> +                          tpm_init_tpm, NULL, 1) != 0) {
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +
> +void tpm_cleanup(void)
> +{
> +    TPMBackend *drv, *next;
> +
> +    QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) {
> +        QLIST_REMOVE(drv, list);
> +        drv->ops->destroy(drv);
> +    }
> +}
> +
> +
> +void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
> +{
> +    QemuOpts *opts;
> +
> +    if (strcmp("none", optarg) != 0) {
> +        if (*optarg == '?') {
> +            tpm_display_backend_drivers(stdout);
> +            exit(0);
> +        }
> +        opts = qemu_opts_parse(opts_list, optarg, 1);
> +        if (!opts) {
> +            exit(1);
> +        }
> +    }
> +}
> +
> +# else /* TARGET_I386 || TARGET_X86_64 */
> +
> +void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
> +{
> +}
> +
> +int tpm_init(void)
> +{
> +    return 0;
> +}
> +
> +void tpm_cleanup(void)
> +{
> +}
> +
> +void do_info_tpm(Monitor *mon)
> +{
> +    monitor_printf(mon, "TPM support: not compiled\n");
> +}
> +
> +# endif
> +#endif /* CONFIG_TPM */
> Index: qemu-git/tpm.h
> ===================================================================
> --- /dev/null
> +++ qemu-git/tpm.h
> @@ -0,0 +1,112 @@
> +#ifndef _HW_TPM_CONFIG_H
> +#define _HW_TPM_CONFIG_H
> +
> +struct TPMState;
> +typedef struct TPMState TPMState;
> +
> +#include "hw/tpm_tis.h"
> +
> +struct TPMDriverOps;
> +typedef struct TPMDriverOps TPMDriverOps;
> +
> +typedef struct TPMBackend {
> +    char *id;
> +    char *model;
> +    TPMDriverOps *ops;
> +
> +    QLIST_ENTRY(TPMBackend) list;
> +} TPMBackend;
> +
> +
> +/* locality data  -- all fields are persisted */
> +typedef struct TPMLocality {
> +    enum tis_state state;
> +    uint8_t access;
> +    uint8_t sts;
> +    uint32_t inte;
> +    uint32_t ints;
> +
> +    uint16_t w_offset;
> +    uint16_t r_offset;
> +    TPMSizedBuffer w_buffer;
> +    TPMSizedBuffer r_buffer;
> +} TPMLocality;
> +
> +
> +/* overall state of the TPM interface */
> +struct TPMState {
> +    ISADevice busdev;
> +
> +    uint32_t offset;
> +    uint8_t buf[TIS_TPM_BUFFER_MAX];
> +
> +    uint8_t active_locty;
> +    uint8_t aborting_locty;
> +    uint8_t next_locty;
> +
> +    uint8_t command_locty;
> +    TPMLocality loc[NUM_LOCALITIES];
> +
> +    qemu_irq irq;
> +    uint32_t irq_num;
> +
> +    QemuMutex state_lock;
> +    QemuCond  from_tpm_cond;
> +    QemuCond  to_tpm_cond;
> +    bool      to_tpm_execute;
> +
> +    bool      tpm_initialized;
> +
> +    char *backend;
> +    TPMBackend *be_driver;
> +};
> +
> +
> +typedef void (TPMRecvDataCB)(TPMState *s, uint8_t locty);
> +
> +struct TPMDriverOps {
> +    const char *id;
> +    /* get a descriptive text of the backend to display to the user */
> +    const char *(*desc)(void);
> +
> +    void (*job_for_main_thread)(void *);
> +
> +    TPMBackend *(*create)(QemuOpts *, const char *id, const char *model);
> +    void (*destroy)(TPMBackend *drv);
> +
> +    /* initialize the backend */
> +    int (*init)(TPMState *s, TPMRecvDataCB *datacb);
> +    /* start up the TPM on the backend early if possible */
> +    int (*early_startup_tpm)(void);
> +    /* start up the TPM on the backend late if necessary */
> +    int (*late_startup_tpm)(void);
> +    /* returns true if nothing will ever answer TPM requests */
> +    bool (*had_startup_error)(void);
> +
> +    size_t (*realloc_buffer)(TPMSizedBuffer *sb);
> +
> +    void (*reset)(void);
> +
> +    /* called to trigger the saving of the volatile data;
> +       called before the VM suspends / migrates */
> +    int (*save_volatile_data)(void);
> +    /* triggers the loading of the volatile data */
> +    int (*load_volatile_data)(TPMState *s);
> +
> +    bool (*get_tpm_established_flag)(void);
> +};
> +
> +#define TPM_DEFAULT_DEVICE_ID    "tpm0"
> +#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
> +
> +void tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
> +int tpm_init(void);
> +void tpm_cleanup(void);
> +void qemu_create_tpm(void);
> +TPMBackend *qemu_find_tpm(const char *id);
> +void do_info_tpm(Monitor *mon);
> +void tpm_display_backend_drivers(FILE *out);
> +const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +
> +
> +#endif /* _HW_TPM_CONFIG_H */
> Index: qemu-git/hw/pc.c
> ===================================================================
> --- qemu-git.orig/hw/pc.c
> +++ qemu-git/hw/pc.c
> @@ -43,6 +43,7 @@
>  #include "ui/qemu-spice.h"
>  #include "memory.h"
>  #include "exec-memory.h"
> +#include "tpm.h"
>  
>  /* output Bochs bios info messages */
>  //#define DEBUG_BIOS
> @@ -62,7 +63,7 @@
>  #define PC_MAX_BIOS_SIZE (4 * 1024 * 1024)
>  
>  /* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables.  */
> -#define ACPI_DATA_SIZE       0x10000
> +#define ACPI_DATA_SIZE       0x20000
>  #define BIOS_CFG_IOPORT 0x510
>  #define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0)
>  #define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1)
> @@ -1183,6 +1184,10 @@ void pc_basic_device_init(qemu_irq *isa_
>          fd[i] = drive_get(IF_FLOPPY, 0, i);
>      }
>      fdctrl_init_isa(fd);
> +
> +#ifdef CONFIG_TPM
> +    qemu_create_tpm();
> +#endif
>  }
>  
>  void pc_pci_device_init(PCIBus *pci_bus)
> Index: qemu-git/monitor.c
> ===================================================================
> --- qemu-git.orig/monitor.c
> +++ qemu-git/monitor.c
> @@ -47,6 +47,7 @@
>  #include "migration.h"
>  #include "kvm.h"
>  #include "acl.h"
> +#include "tpm.h"
>  #include "qint.h"
>  #include "qfloat.h"
>  #include "qlist.h"
> @@ -3151,6 +3152,15 @@ static const mon_cmd_t info_cmds[] = {
>          .mhandler.info = do_info_trace_events,
>      },
>  #endif
> +#if defined(CONFIG_TPM)
> +    {
> +        .name       = "tpm",
> +        .args_type  = "",
> +        .params     = "",
> +        .help       = "show the TPM devices",
> +        .mhandler.info = do_info_tpm,
> +    },
> +#endif
>      {
>          .name       = NULL,
>      },
> Index: qemu-git/hmp-commands.hx
> ===================================================================
> --- qemu-git.orig/hmp-commands.hx
> +++ qemu-git/hmp-commands.hx
> @@ -1351,6 +1351,8 @@ show device tree
>  show qdev device model list
>  @item info roms
>  show roms
> +@item info tpm
> +show the TPM devices
>  @end table
>  ETEXI
>  
> Index: qemu-git/Makefile.target
> ===================================================================
> --- qemu-git.orig/Makefile.target
> +++ qemu-git/Makefile.target
> @@ -200,6 +200,7 @@ obj-$(CONFIG_KVM) += kvm.o kvm-all.o
>  obj-$(CONFIG_NO_KVM) += kvm-stub.o
>  obj-y += memory.o
>  LIBS+=-lz
> +obj-y += tpm.o
>  
>  QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
>  QEMU_CFLAGS += $(VNC_SASL_CFLAGS)
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver Stefan Berger
@ 2011-09-01 17:20   ` Michael S. Tsirkin
  2011-09-02  1:12     ` Stefan Berger
  2011-09-09 21:13   ` Paul Moore
  1 sibling, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:20 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:54AM -0400, Stefan Berger wrote:
> This patch adds support for handling of persistent state to the TPM TIS
> frontend.
> 
> The currently used buffer is determined (can only be in currently active
> locality and either be a read or a write buffer) and only that buffer's content
> is stored. The reverse is done when the state is restored from disk
> where the buffer's content are copied into the currently used buffer.
> 
> To keep compatibility with existing Xen implementation the VMStateDescription
> was adapted to be compatible with existing state. For that I am adding Andreas
> Niederl as an author to the file.
> 
> v5:
>  - removing qdev.no_user=1
> 
> v4:
>  - main thread releases the 'state' lock while periodically calling the
>    backends function that may request it to write data into block storage.
> 
> v3:
>  - all functions prefixed with tis_
>  - while the main thread is waiting for an outstanding TPM command to finish,
>    it periodically does some work (writes data to the block storage)
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> 
> ---
>  hw/tpm_tis.c |  166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 166 insertions(+)
> 
> Index: qemu-git/hw/tpm_tis.c
> ===================================================================
> --- qemu-git.orig/hw/tpm_tis.c
> +++ qemu-git/hw/tpm_tis.c
> @@ -6,6 +6,8 @@
>   * Author: Stefan Berger <stefanb@us.ibm.com>
>   *         David Safford <safford@us.ibm.com>
>   *
> + * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
> + *
>   * This program is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU General Public License as
>   * published by the Free Software Foundation, version 2 of the
> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
>   err_exit:
>      return -1;
>  }
> +
> +/* persistent state handling */
> +
> +static void tis_pre_save(void *opaque)
> +{
> +    TPMState *s = opaque;
> +    uint8_t locty = s->active_locty;
> +
> +    qemu_mutex_lock(&s->state_lock);
> +
> +    /* wait for outstanding requests to complete */
> +    if (IS_VALID_LOCTY(locty) && s->loc[locty].state == STATE_EXECUTION) {
> +        if (!s->be_driver->ops->job_for_main_thread) {
> +            qemu_cond_wait(&s->from_tpm_cond, &s->state_lock);
> +        } else {
> +            while (s->loc[locty].state == STATE_EXECUTION) {
> +                qemu_mutex_unlock(&s->state_lock);
> +
> +                s->be_driver->ops->job_for_main_thread(NULL);
> +                usleep(10000);
> +
> +                qemu_mutex_lock(&s->state_lock);
> +            }
> +        }
> +    }
> +
> +#ifdef DEBUG_TIS_SR
> +    fprintf(stderr,
> +            "tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = %d\n",
> +            s->loc[0].r_offset, s->loc[0].w_offset);
> +    if (s->loc[0].r_offset) {
> +        tis_dump_state(opaque, 0);
> +    }
> +#endif
> +
> +    qemu_mutex_unlock(&s->state_lock);
> +
> +    /* copy current active read or write buffer into the buffer
> +       written to disk */
> +    if (IS_VALID_LOCTY(locty)) {
> +        switch (s->loc[locty].state) {
> +        case STATE_RECEPTION:
> +            memcpy(s->buf,
> +                   s->loc[locty].w_buffer.buffer,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].w_buffer.size));
> +            s->offset = s->loc[locty].w_offset;
> +        break;
> +        case STATE_COMPLETION:
> +            memcpy(s->buf,
> +                   s->loc[locty].r_buffer.buffer,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].r_buffer.size));
> +            s->offset = s->loc[locty].r_offset;
> +        break;
> +        default:
> +            /* leak nothing */
> +            memset(s->buf, 0x0, sizeof(s->buf));
> +        break;
> +        }
> +    }
> +
> +    s->be_driver->ops->save_volatile_data();
> +}
> +
> +
> +static int tis_post_load(void *opaque,
> +                         int version_id __attribute__((unused)))
> +{
> +    TPMState *s = opaque;
> +
> +    uint8_t locty = s->active_locty;
> +
> +    if (IS_VALID_LOCTY(locty)) {
> +        switch (s->loc[locty].state) {
> +        case STATE_RECEPTION:
> +            memcpy(s->loc[locty].w_buffer.buffer,
> +                   s->buf,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].w_buffer.size));
> +            s->loc[locty].w_offset = s->offset;
> +        break;
> +        case STATE_COMPLETION:
> +            memcpy(s->loc[locty].r_buffer.buffer,
> +                   s->buf,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].r_buffer.size));
> +            s->loc[locty].r_offset = s->offset;
> +        break;
> +        default:
> +        break;
> +        }
> +    }

Should this do something with interrupts as well?

> +
> +#ifdef DEBUG_TIS_SR
> +    fprintf(stderr,
> +            "tpm_tis: resume : locty 0 : r_offset = %d, w_offset = %d\n",
> +            s->loc[0].r_offset, s->loc[0].w_offset);
> +#endif
> +
> +    return s->be_driver->ops->load_volatile_data(s);
> +}
> +
> +
> +static const VMStateDescription vmstate_locty = {
> +    .name = "loc",
> +    .version_id = 1,
> +    .minimum_version_id = 0,
> +    .minimum_version_id_old = 0,
> +    .fields      = (VMStateField[]) {
> +        VMSTATE_UINT32(state, TPMLocality),
> +        VMSTATE_UINT32(inte, TPMLocality),
> +        VMSTATE_UINT32(ints, TPMLocality),
> +        VMSTATE_UINT8(access, TPMLocality),
> +        VMSTATE_UINT8(sts, TPMLocality),
> +        VMSTATE_END_OF_LIST(),
> +    }
> +};
> +
> +
> +static const VMStateDescription vmstate_tis = {
> +    .name = "tpm",
> +    .version_id = 1,
> +    .minimum_version_id = 0,
> +    .minimum_version_id_old = 0,
> +    .pre_save  = tis_pre_save,
> +    .post_load = tis_post_load,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(irq_num, TPMState),
> +        VMSTATE_UINT32(offset, TPMState),
> +        VMSTATE_BUFFER(buf, TPMState),
> +        VMSTATE_UINT8(active_locty, TPMState),
> +        VMSTATE_UINT8(aborting_locty, TPMState),
> +        VMSTATE_UINT8(next_locty, TPMState),

Is irq_num guest modifiable?
If yes post load should do something with it?
If not, why are we migrating it?

> +
> +        VMSTATE_STRUCT_ARRAY(loc, TPMState, NUM_LOCALITIES, 1,
> +                             vmstate_locty, TPMLocality),
> +
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +
> +static ISADeviceInfo tis_device_info = {
> +    .init         = tis_init,
> +    .qdev.name    = "tpm-tis",
> +    .qdev.size    = sizeof(TPMState),
> +    .qdev.vmsd    = &vmstate_tis,
> +    .qdev.reset   = tis_reset,
> +    .qdev.props = (Property[]) {
> +        DEFINE_PROP_UINT32("irq", TPMState,
> +                           irq_num, TPM_TIS_IRQ),
> +        DEFINE_PROP_STRING("tpmdev", TPMState, backend),
> +        DEFINE_PROP_END_OF_LIST(),
> +    },
> +};
> +
> +
> +static void tis_register_device(void)
> +{
> +    isa_qdev_register(&tis_device_info);
> +}
> +
> +device_init(tis_register_device)
> 

So this is a qdev device. Why do we need a new flag to set it up then?

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process Stefan Berger
@ 2011-09-01 17:23   ` Michael S. Tsirkin
  2011-09-02  1:16     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:23 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:55AM -0400, Stefan Berger wrote:
> The TPM interface (tpm_tis) needs to be explicitly enabled via
> ./configure --enable-tpm. This patch also restricts the building of the
> TPM support to i386 and x86_64 targets since only there it is currently
> supported. This prevents that one will end up with support for a frontend
> but no available backend.

This can happen anyway - just don't load the tpms driver :)
Presumably if libtpms exists on the system, we should
assume it's there for a reason. configure should test
that IMO and not limit architectures artificially.

> 
> v3:
>  - fixed and moved hunks in Makefile.target into right place
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> Index:qemu/Makefile.target
> ===================================================================
> ---
>  Makefile.target |    1 +
>  configure       |   20 ++++++++++++++++++++
>  2 files changed, 21 insertions(+)
> 
> Index: qemu-git/Makefile.target
> ===================================================================
> --- qemu-git.orig/Makefile.target
> +++ qemu-git/Makefile.target
> @@ -233,6 +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
>  
>  # shared objects
>  obj-ppc-y = ppc.o
> Index: qemu-git/configure
> ===================================================================
> --- qemu-git.orig/configure
> +++ qemu-git/configure
> @@ -183,6 +183,7 @@ usb_redir=""
>  opengl=""
>  zlib="yes"
>  guest_agent="yes"
> +tpm="no"
>  
>  # parse CC options first
>  for opt do
> @@ -765,6 +766,8 @@ for opt do
>    ;;
>    --disable-guest-agent) guest_agent="no"
>    ;;
> +  --enable-tpm) tpm="yes"
> +  ;;
>    *) echo "ERROR: unknown option $opt"; show_help="yes"
>    ;;
>    esac
> @@ -1044,6 +1047,7 @@ echo "  --disable-usb-redir      disable
>  echo "  --enable-usb-redir       enable usb network redirection support"
>  echo "  --disable-guest-agent    disable building of the QEMU Guest Agent"
>  echo "  --enable-guest-agent     enable building of the QEMU Guest Agent"
> +echo "  --enable-tpm             enable an emulated TPM"
>  echo ""
>  echo "NOTE: The object files are built at the place where configure is launched"
>  exit 1
> @@ -2731,6 +2735,7 @@ echo "nss used          $smartcard_nss"
>  echo "usb net redir     $usb_redir"
>  echo "OpenGL support    $opengl"
>  echo "build guest agent $guest_agent"
> +echo "TPM support       $tpm"
>  
>  if test "$sdl_too_old" = "yes"; then
>  echo "-> Your SDL version is too old - please upgrade to have SDL support"
> @@ -3555,6 +3560,21 @@ if test "$gprof" = "yes" ; then
>    fi
>  fi
>  
> +if test "$tpm" = "yes"; then
> +  has_tpm=0
> +  if test "$target_softmmu" = "yes" ; then
> +    case "$TARGET_BASE_ARCH" in
> +    i386)
> +      has_tpm=1
> +    ;;
> +    esac
> +  fi
> +
> +  if test "$has_tpm" = "1"; then
> +      echo "CONFIG_TPM=y" >> $config_host_mak
> +  fi
> +fi
> +
>  linker_script="-Wl,-T../config-host.ld -Wl,-T,\$(SRC_PATH)/\$(ARCH).ld"
>  if test "$target_linux_user" = "yes" -o "$target_bsd_user" = "yes" ; then
>    case "$ARCH" in
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend Stefan Berger
@ 2011-09-01 17:27   ` Michael S. Tsirkin
  2011-09-02  1:24     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:27 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:58AM -0400, Stefan Berger wrote:
> 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 connects 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 is registered with
> libtpms. Those callback functions are invoked by libtpms for example to
> store the TPM's state.
> 
> 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.
> 
> v6:
>   - cache a copy of the last permanent state blob
>   - move some functions into tpm_builtin.h
>   - 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.
>   - add out_len variable used in the thread
> 
> v5:
>   - check access() to TPM's state file and report error if file is not
>     accessible
> 
> 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 <stefanb@linux.vnet.ibm.com>
> 
> ---
>  configure        |    1 
>  hw/tpm_builtin.c |  450 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
>  hw/tpm_builtin.h |   56 ++++++
>  3 files changed, 482 insertions(+), 25 deletions(-)
> 
> 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

Just wondering - might a stub driver be useful for
basic testing on systems without TPM hardware?

The namespace comment applies to this and all other patches.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer Stefan Berger
@ 2011-09-01 17:32   ` Michael S. Tsirkin
  2011-09-02  1:53     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:32 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:59AM -0400, Stefan Berger wrote:
> This patch introduces file locking via fcntl() for the block layer so that
> concurrent access to files shared by 2 Qemu instances, for example via NFS,
> can be serialized. This feature is useful primarily during initial phases of
> VM migration where the target machine's TIS driver validates the block
> storage (and in a later patch checks for missing AES keys) and terminates
> Qemu if the storage is found to be faulty. This then allows migration to
> be gracefully terminated and Qemu continues running on the source machine.
> 
> Support for win32 is based on win32 API and has been lightly tested with a
> standalone test program locking shared storage from two different machines.
> 
> To enable locking a file multiple times, a counter is used. Actual locking
> happens the very first time and unlocking happens when the counter is zero.
> 
> v7:
>  - fixed compilation error in win32 part
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

Generally, what all other devices do is perform validation
as the last step in migration when device state
is restored. On failure, management can decide what to do:
retry migration or restart on source.

Why is TPM special and needs to be treated differently?



> ---
> 
> ---
>  block.c           |   41 +++++++++++++++++++++++++++++++++++
>  block.h           |    8 ++++++
>  block/raw-posix.c |   63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  block/raw-win32.c |   52 ++++++++++++++++++++++++++++++++++++++++++++
>  block_int.h       |    4 +++
>  5 files changed, 168 insertions(+)
> 
> Index: qemu-git/block.c
> ===================================================================
> --- qemu-git.orig/block.c
> +++ qemu-git/block.c
> @@ -521,6 +521,8 @@ static int bdrv_open_common(BlockDriverS
>          goto free_and_fail;
>      }
>  
> +    drv->num_locks = 0;
> +
>      bs->keep_read_only = bs->read_only = !(open_flags & BDRV_O_RDWR);
>  
>      ret = refresh_total_sectors(bs, bs->total_sectors);
> @@ -1316,6 +1318,45 @@ void bdrv_get_geometry(BlockDriverState 
>      *nb_sectors_ptr = length;
>  }
>  
> +/* file locking */
> +static int bdrv_lock_common(BlockDriverState *bs, BDRVLockType lock_type)
> +{
> +    BlockDriver *drv = bs->drv;
> +
> +    if (!drv) {
> +        return -ENOMEDIUM;
> +    }
> +
> +    if (bs->file) {
> +        drv = bs->file->drv;
> +        if (drv->bdrv_lock) {
> +            return drv->bdrv_lock(bs->file, lock_type);
> +        }
> +    }
> +
> +    if (drv->bdrv_lock) {
> +        return drv->bdrv_lock(bs, lock_type);
> +    }
> +
> +    return -ENOTSUP;
> +}
> +
> +
> +int bdrv_lock(BlockDriverState *bs)
> +{
> +    if (bdrv_is_read_only(bs)) {
> +        return bdrv_lock_common(bs, BDRV_F_RDLCK);
> +    }
> +
> +    return bdrv_lock_common(bs, BDRV_F_WRLCK);
> +}
> +
> +void bdrv_unlock(BlockDriverState *bs)
> +{
> +    bdrv_lock_common(bs, BDRV_F_UNLCK);
> +}
> +
> +
>  struct partition {
>          uint8_t boot_ind;           /* 0x80 - active */
>          uint8_t head;               /* starting head */
> Index: qemu-git/block.h
> ===================================================================
> --- qemu-git.orig/block.h
> +++ qemu-git/block.h
> @@ -43,6 +43,12 @@ typedef struct QEMUSnapshotInfo {
>  #define BDRV_SECTOR_MASK   ~(BDRV_SECTOR_SIZE - 1)
>  
>  typedef enum {
> +    BDRV_F_UNLCK,
> +    BDRV_F_RDLCK,
> +    BDRV_F_WRLCK,
> +} BDRVLockType;
> +
> +typedef enum {
>      BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC,
>      BLOCK_ERR_STOP_ANY
>  } BlockErrorAction;
> @@ -100,6 +106,8 @@ int bdrv_commit(BlockDriverState *bs);
>  void bdrv_commit_all(void);
>  int bdrv_change_backing_file(BlockDriverState *bs,
>      const char *backing_file, const char *backing_fmt);
> +int bdrv_lock(BlockDriverState *bs);
> +void bdrv_unlock(BlockDriverState *bs);
>  void bdrv_register(BlockDriver *bdrv);
>  
>  
> Index: qemu-git/block/raw-posix.c
> ===================================================================
> --- qemu-git.orig/block/raw-posix.c
> +++ qemu-git/block/raw-posix.c
> @@ -803,6 +803,67 @@ static int64_t raw_get_allocated_file_si
>      return (int64_t)st.st_blocks * 512;
>  }
>  
> +static int raw_lock(BlockDriverState *bs, BDRVLockType lock_type)
> +{
> +    BlockDriver *drv = bs->drv;
> +    BDRVRawState *s = bs->opaque;
> +    struct flock flock = {
> +        .l_whence = SEEK_SET,
> +        .l_start = 0,
> +        .l_len = 0,
> +    };
> +    int n;
> +
> +    switch (lock_type) {
> +    case BDRV_F_RDLCK:
> +    case BDRV_F_WRLCK:
> +        if (drv->num_locks) {
> +            drv->num_locks++;
> +            return 0;
> +        }
> +        flock.l_type = (lock_type == BDRV_F_RDLCK) ? F_RDLCK : F_WRLCK;
> +        break;
> +
> +    case BDRV_F_UNLCK:
> +        if (--drv->num_locks > 0) {
> +            return 0;
> +        }
> +
> +        assert(drv->num_locks == 0);
> +
> +        flock.l_type = F_UNLCK;
> +        break;
> +
> +    default:
> +        return -EINVAL;
> +    }
> +
> +    while (1) {
> +        n = fcntl(s->fd, F_SETLKW, &flock);
> +        if (n < 0) {
> +            if (errno == EINTR) {
> +                continue;
> +            }
> +            if (errno == EAGAIN) {
> +                usleep(10000);
> +                continue;
> +            }
> +        }
> +        break;
> +    }
> +
> +    if (n == 0 &&
> +        ((lock_type == BDRV_F_RDLCK) || (lock_type == BDRV_F_WRLCK))) {
> +        drv->num_locks = 1;
> +    }
> +
> +    if (n) {
> +        return -errno;
> +    }
> +
> +    return 0;
> +}
> +
>  static int raw_create(const char *filename, QEMUOptionParameter *options)
>  {
>      int fd;
> @@ -901,6 +962,8 @@ static BlockDriver bdrv_file = {
>      .bdrv_get_allocated_file_size
>                          = raw_get_allocated_file_size,
>  
> +    .bdrv_lock = raw_lock,
> +
>      .create_options = raw_create_options,
>  };
>  
> Index: qemu-git/block_int.h
> ===================================================================
> --- qemu-git.orig/block_int.h
> +++ qemu-git/block_int.h
> @@ -146,6 +146,10 @@ struct BlockDriver {
>       */
>      int (*bdrv_has_zero_init)(BlockDriverState *bs);
>  
> +    /* File locking */
> +    int num_locks;
> +    int (*bdrv_lock)(BlockDriverState *bs, BDRVLockType lock_type);
> +
>      QLIST_ENTRY(BlockDriver) list;
>  };
>  
> Index: qemu-git/block/raw-win32.c
> ===================================================================
> --- qemu-git.orig/block/raw-win32.c
> +++ qemu-git/block/raw-win32.c
> @@ -242,6 +242,57 @@ static int64_t raw_get_allocated_file_si
>      return st.st_size;
>  }
>  
> +static int raw_lock(BlockDriverState *bs, int lock_type)
> +{
> +    BlockDriver *drv = bs->drv;
> +    BDRVRawState *s = bs->opaque;
> +    OVERLAPPED ov;
> +    BOOL res;
> +    DWORD num_bytes;
> +
> +    switch (lock_type) {
> +    case BDRV_F_RDLCK:
> +    case BDRV_F_WRLCK:
> +        if (drv->num_locks) {
> +            drv->num_locks++;
> +            return 0;
> +        }
> +
> +        memset(&ov, 0, sizeof(ov));
> +
> +        res = LockFileEx(s->hfile, LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, ~0, &ov);
> +
> +        if (res == FALSE) {
> +            res = GetOverlappedResult(s->hfile, &ov, &num_bytes, TRUE);
> +        }
> +
> +        if (res == TRUE) {
> +            drv->num_locks = 1;
> +        }
> +
> +        break;
> +
> +    case BDRV_F_UNLCK:
> +        if (--drv->num_locks > 0) {
> +            return 0;
> +        }
> +
> +        assert(drv->num_locks >= 0);
> +
> +        res = UnlockFile(s->hfile, 0, 0, ~0, ~0);
> +        break;
> +
> +    default:
> +        return -EINVAL;
> +    }
> +
> +    if (res == FALSE) {
> +        return -EIO;
> +    }
> +
> +    return 0;
> +}
> +
>  static int raw_create(const char *filename, QEMUOptionParameter *options)
>  {
>      int fd;
> @@ -289,6 +340,7 @@ static BlockDriver bdrv_file = {
>      .bdrv_get_allocated_file_size
>                          = raw_get_allocated_file_size,
>  
> +    .bdrv_lock		= raw_lock,
>      .create_options = raw_create_options,
>  };
>  
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation Stefan Berger
@ 2011-09-01 17:40   ` Michael S. Tsirkin
  2011-09-02  2:41     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 17:40 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:36:04AM -0400, Stefan Berger wrote:
> 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.

Why limit this to intel platforms then?

> v8:
>  - initializing 'in' variable
> 
> Signed-off-by: Stefan Berger <stefanb@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

Is this the same one an earlier patch removed?

> + *
> + *  Copyright (c) 2010, 2011 IBM Corporation
> + *  Copyright (c) 2010, 2011 Stefan Berger

Why dual copyright btw?

> + *
> + * 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 */

don't use C++ comments please.

> +
> +
> +/* 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;

this lacks consistency in naming.
prefix everything with tpm_null?

> +
> +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];

Use symbolic constants above?

> +
> +
> +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

Hmm, so now we remove some code added earlier?
Pls don't do this for new code: this makes review harder, not easier.
It makes sense for old code to ensure bisectability.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state Stefan Berger
@ 2011-09-01 18:10   ` Michael S. Tsirkin
  2011-09-01 19:01     ` Michael S. Tsirkin
  2011-09-02  3:00     ` Stefan Berger
  0 siblings, 2 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 18:10 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:36:05AM -0400, Stefan Berger wrote:
> This patch adds a -tpm ...,initstate=...,... command line option to the
> TPM's existing options and enables the TPM to be initialized with an
> existing state blob. This in turn allows us to simulate TPM manufacturing
> and equip the TPM with an endorsement key, certificates and initialize its
> NVRAM areas etc.. This step is typically done during manufacturng of the TPM
> and/or the (physical) machine.
> 
> The initial state can be passed either as file or via a file descriptor. The
> encoding of the state can either be binary or in form of a base64-encoded
> blob surrounded by tags indicating the start and end.
> 
> The intial state can be produced through a yet-to-be-published tpm-authoring
> tool.
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>

I am guessing we get the base64 format from tpmlib?

> ---
>  hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  qemu-config.c    |   12 +++++
>  qemu-options.hx  |   40 ++++++++++++++++-
>  3 files changed, 172 insertions(+), 3 deletions(-)
> 
> Index: qemu-git/hw/tpm_builtin.c
> ===================================================================
> --- qemu-git.orig/hw/tpm_builtin.c
> +++ qemu-git/hw/tpm_builtin.c
> @@ -170,9 +170,16 @@ static struct enckey {
>      AES_KEY tpm_dec_key;
>  } enckey;
>  
> +static int tpm_initstatefd = -1;
> +static bool tpm_initstate_bin;
> +

This hardcodes assumption of a single backend.

>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>                                                 enum BSEntryType be,
>                                                 TPMSizedBuffer *tsb);
> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
> +                                                uint32_t *length,
> +                                                size_t tpm_number,
> +                                                const char *name);
>  
>  
>  
> @@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load
>          *length = permanent_state.size;
>  
>          if (*length == 0) {
> -            rc = TPM_RETRY;
> +            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
>          } else {
>              /* keep a copy of the last permanent state */
>              rc = TPM_Malloc(data, *length);
> @@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys
>  }
>  
>  
> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
> +                                                uint32_t *length,
> +                                                size_t tpm_number,
> +                                                const char *name)
> +{
> +    TPM_RESULT rc = TPM_RETRY;
> +    uint32_t allocated = 0;
> +    int len, flags;
> +    unsigned char buf[1024];
> +    unsigned char *result = NULL;
> +    size_t result_len;
> +
> +    if (tpm_initstatefd >= 0) {
> +        *data = NULL;
> +        *length = 0;
> +
> +        flags = fcntl(tpm_initstatefd, F_GETFL);
> +        if (flags < 0 ||
> +            fcntl(tpm_initstatefd, F_SETFL, flags & ~O_NONBLOCK) < 0) {
> +            return TPM_FAIL;
> +        }
> +
> +        while (TRUE) {
> +            len = read(tpm_initstatefd, buf, sizeof(buf));
> +
> +            if (len > 0) {
> +                if (len > allocated - *length) {
> +                    allocated = *length + len + 1024;
> +                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
> +                        goto err_exit;
> +                    }
> +                }
> +                memcpy(&(*data)[*length], buf, len);
> +                *length += len;
> +                (*data)[*length] = 0;
> +            } else if (len == 0) {
> +                rc = TPM_SUCCESS;
> +                break;
> +            } else if (len < 0) {
> +                if (errno == EINTR) {
> +                    continue;
> +                }
> +                goto err_exit;
> +            }
> +        }
> +
> +        if (*data == NULL) {
> +            /* nothing read */
> +            rc = TPM_FAIL;
> +            goto err_exit;
> +        }
> +
> +        if (!tpm_initstate_bin) {
> +            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
> +                                  &result, &result_len) != TPM_SUCCESS) {
> +                goto err_exit;
> +            }
> +            TPM_Free(*data);
> +            *data = result;
> +            *length = result_len;
> +            result = NULL;
> +        }
> +        /* sanity check for the size of the blob */
> +        if (*length > tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
> +            goto err_exit;
> +        }

Do we really have to hand-craft file reading?
How large is TPMPROP_TPM_MAX_NV_SPACE?
If not too large, we can just allocate that
and do a single fread call?

Or, we rely on glib now - can we use 
g_io_channel_read_to_end () or something like that?



> +        /* have it written into the BlockStorage */
> +        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);

What if that backend is compiled-out?
link will fail?

> +        if (rc != TPM_SUCCESS) {
> +            goto err_exit;
> +        }
> +    }
> +
> +norm_exit:
> +    close(tpm_initstatefd);
> +    tpm_initstatefd = -1;
> +
> +    return rc;
> +
> +err_exit:
> +    TPM_Free(*data);
> +    *data = NULL;
> +    *length = 0;
> +    TPM_Free(result);
> +
> +    goto norm_exit;
> +}
> +
>  /*****************************************************************/
>  
>  
> @@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe
>      const char *value;
>      unsigned char keyvalue[256/8];
>      int keysize = sizeof(keyvalue);
> +    unsigned int offset;
>  
>      driver = g_malloc(sizeof(TPMBackend));
>      if (!driver) {
> @@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe
>          enckey.enctype = BS_DIR_ENCTYPE_NONE;
>      }
>  
> +    value = qemu_opt_get(opts, "initstate");
> +    if (value) {
> +        offset = 0;
> +
> +        if (!strncmp(value, "bin:", 4)) {
> +            tpm_initstate_bin = true;
> +            offset = 4;
> +        } else if (!strncmp(value, "base64:", 7)) {
> +            tpm_initstate_bin = false;
> +            offset = 7;
> +        }
> +
> +        if (sscanf(&value[offset], "fd:%d", &tpm_initstatefd) != 1) {
> +            tpm_initstatefd = open(&value[offset], O_RDONLY);
> +            if (tpm_initstatefd < 0) {
> +                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
> +                        value);
> +                goto err_exit;
> +            }
> +        }
> +    }
> +

Separate options for fd and for file mode would be better.

>      return driver;
>  
>  err_exit:
> @@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke
>      g_free(driver->id);
>      g_free(driver->model);
>      g_free(driver);
> +
> +    close(tpm_initstatefd);
> +    tpm_initstatefd = -1;
>  }
>  
>  
> Index: qemu-git/qemu-config.c
> ===================================================================
> --- qemu-git.orig/qemu-config.c
> +++ qemu-git/qemu-config.c
> @@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Data encryption key",
>          },
> +        {
> +            .name = "initstate",
> +            .type = QEMU_OPT_STRING,
> +            .help = "File or file descriptor for reading initial TPM state "
> +                    "from",
> +        },
>          { /* end of list */ }
>      },
>  };
> @@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Data encryption key",
>          },
> +        {
> +            .name = "initstate",
> +            .type = QEMU_OPT_STRING,
> +            .help = "File or file descriptor for reading initial TPM state "
> +                    "from",
> +        },
>          { /* end of list */ }
>      },
>  };

I think description should document the magic bin:/base64: etc strings,
or better get rid of them.

> Index: qemu-git/qemu-options.hx
> ===================================================================
> --- qemu-git.orig/qemu-options.hx
> +++ qemu-git/qemu-options.hx
> @@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:)
>  DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>      "" \
>      "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
> +    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
>      "                enable a builtin TPM with state in file in path\n" \
>      "                and encrypt the TPM's state with the given AES key\n" \
> +    "                initstate= path to initial state of TPM; default is base64\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" \
> @@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend
>  qemu -tpmdev ?
>  @end example
>  
> -@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
> +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
>  
>  Creates an instance of the built-in TPM.
>  
> @@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t
>  -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
>  @end example
>  
> +@option{initstate} specifies the path to a file containing the initial
> +state of the TPM. It can be used to provide the TPM with an EK and certificates
> +for the EK, TPM and Platform. Since the file contains binary data that
> +have to conform to the TPM's layout of data, it must have been created using
> +an approriate authoring tool.
> +
> +The initstate option allows to provide a binary state blob or one that is
> +encode in base 64. The base64-encode state blob must have the format
> +
> +@example
> +-----BEGIN INITSTATE-----
> +<base 64 encoded state>
> +-----END INITSTATE-----
> +@end example
> +
> +The initstate option is only effective when Qemu is started with blank
> +state.
> +
> +The initstate option supports several formats:
> +
> +@table @option
> + @item  [base64:]<path_to_blob>
> + Provide the path to the TPM's initial state blob in base64 format.
> + @item  bin:<path to blob>
> + Provide the path to the TPM's initial state blob in binary format.
> + @item  [base64:]fd:<fd>
> + Provide the base64 formatted initial state via a file descriptor to read from.
> + @item  bin:fd:<fd>
> + Provide the binary initial state via a file descriptor to read from.

The command line is non standard.  E.g. what if the path starts with fd?

> +@end table
> +
> +@option{initstate} is optional.
> +
> +
>  @item -tpmdev null
>  
>  Creates an instance of a TPM null driver that responds to every command
> @@ -1840,7 +1876,7 @@ with a fault message.
>  The short form of a TPM device option is:
>  @table @option
>  
> -@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
> +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}] [,initstate=@var{path}]
>  @findex -tpm
>  
>  @option{model} specifies the device model. The default device model is a
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration
  2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
                   ` (13 preceding siblings ...)
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state Stefan Berger
@ 2011-09-01 18:12 ` Michael S. Tsirkin
  2011-09-02  3:02   ` Stefan Berger
  14 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 18:12 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:51AM -0400, Stefan Berger wrote:
> Once TPM support is check in to the Qemu git repository, I would like to
> force the usage of libtpms-0.5.2.

So configure will need to test that and disable the backend.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
  2011-09-01 17:14   ` Michael S. Tsirkin
@ 2011-09-01 18:14   ` Michael S. Tsirkin
  2011-09-02  1:02     ` Stefan Berger
  1 sibling, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 18:14 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:35:52AM -0400, Stefan Berger wrote:
> This patch adds support for TPM command line options.
> The command line supported here (considering the libtpms based
> backend) are
> 
> ./qemu-... -tpm builtin,path=<path to blockstorage file>
> 
> and
> 
> ./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
>            -device tpm-tis,tpmdev=<id>
> 

builtin is a weird name in fact. Rename to libtpms?

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-01 18:10   ` Michael S. Tsirkin
@ 2011-09-01 19:01     ` Michael S. Tsirkin
  2011-09-02  3:00     ` Stefan Berger
  1 sibling, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 19:01 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 09:10:24PM +0300, Michael S. Tsirkin wrote:
> Do we really have to hand-craft file reading?
> How large is TPMPROP_TPM_MAX_NV_SPACE?
> If not too large, we can just allocate that
> and do a single fread call?
> 
> Or, we rely on glib now - can we use 
> g_io_channel_read_to_end () or something like that?

Or scanf 'a' conversion - it seems to be a gnu extension
but we rely on gcc anyway.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption Stefan Berger
@ 2011-09-01 19:26   ` Michael S. Tsirkin
  2011-09-02  2:23     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-01 19:26 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Aug 31, 2011 at 10:36:01AM -0400, Stefan Berger wrote:
> This patch adds encryption of the individual state blobs that are written
> into the block storage. The 'directory' at the beginnig of the block
> storage is not encrypted.

Does this mean that there's a new format that we store data
in, and that qemu needs to support?
If so, this needs an entry under docs documenting the format.

> 
> The encryption support added in this patch would also work if QCoW2 was not
> to be used as the (only) image file format to store the TPM's state.

What does 'was not to be used' above mean?

> 
> Keys can be passed as a string of hexadecimal digits forming a 256, 192 or
> 128 bit AES key. The string can optionally start with '0x'. If the
> parser does not recognize it as a hexadecimal number, the string itself is
> taken as the AES key, which makes for example 'my_key' a valid AES key
> parameter. It is also necessary to provide the encryption scheme.
> Currently only 'aes-cbc' is supported.  An example for a valid key command
> line argument is:
> 
> -tpm builtin,key=aes-cbc:0x1234567890abcdef123456
> 
> The key passed via command line argument is wiped from the command
> line after parsing. If for example key=aes-cbc:0x1234... was passed it will
> then be changed to key=------... so that 'ps' does not show the key anymore.
> Obviously it cannot be completely prevented that the key is visible during a
> very short period of time until qemu gets to the point where the code wiping
> the key is reached.
> 
> A byte indicating the encryption type being used is introduced in the
> directory structure indicating whether blobs are encrypted and if so, what
> encryption type was used, i.e., aes-cbc.
> 
> An additional 'layer' for reading and writing the blobs to the underlying
> block storage is added. This layer encrypts the blobs for writing if a key is
> available. Similarly it decrypts the blobs after reading.
> 
> Checks are added that test
> - whether encryption is supported follwing the revision of the directory
>   structure (rev >= 2)

You never generate rev 1 code, right?
So why keep that support around in code?
The first version merged into qemu should be revision 0 (or 1, as you like).
Don't support legacy with old version of your patch.

> - whether a key has been provided although all data are stored in clear-text
> - whether a key is missing for decryption.
> 
> In either one of the cases the backend reports an error message to the user
> and Qemu terminates.
> 
> -v7:
>   - cleaned up function parsing key
> 
> -v6:
>   - changed the format of the key= to take the type of encryption into
>     account: key=aes-cbc:0x12345... and reworked code for encryption and
>     decryption of blobs;

separate type and data:
keytype=aes-cbc,key=0x123  to avoid introducing more option parsing.
Also, are people likely to have the key in a file?
If yes maybe read a key from there and skip parsing completely?


>   - modified directory entry to hold a uint_8 describing the encryption
>     type (none, aes-cbc) being used for the blobs.
>   - incrementing revision of the directory to '2' indicating encryption
>     support
>     
> -v5:
>   - -tpmdev now also gets a key parameter
>   - add documentation about key parameter
> 
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> 
> ---
>  hw/tpm_builtin.c |  285 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
>  qemu-config.c    |   10 +
>  qemu-options.hx  |   22 +++-
>  tpm.c            |   10 +
>  4 files changed, 318 insertions(+), 9 deletions(-)
> 
> Index: qemu-git/hw/tpm_builtin.c
> ===================================================================
> --- qemu-git.orig/hw/tpm_builtin.c
> +++ qemu-git/hw/tpm_builtin.c
> @@ -27,6 +27,7 @@
>  #include "hw/pc.h"
>  #include "migration.h"
>  #include "sysemu.h"
> +#include "aes.h"
>  
>  #include <libtpms/tpm_library.h>
>  #include <libtpms/tpm_error.h>
> @@ -110,14 +111,27 @@ typedef struct BSDir {
>      uint16_t  rev;
>      uint32_t  checksum;
>      uint32_t  num_entries;
> -    uint32_t  reserved[10];
> +    uint8_t   enctype;
> +    uint8_t   reserved1[3];
> +    uint32_t  reserved[8];
>      BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
>  } __attribute__((packed)) BSDir;
>  
>  
>  #define BS_DIR_REV1         1
> +/* rev 2 added encryption */
> +#define BS_DIR_REV2         2
>  
> -#define BS_DIR_REV_CURRENT  BS_DIR_REV1
> +
> +#define BS_DIR_REV_CURRENT  BS_DIR_REV2
> +
> +/* above enctype */
> +enum BSEnctype {
> +    BS_DIR_ENCTYPE_NONE = 0,
> +    BS_DIR_ENCTYPE_AES_CBC,
> +
> +    BS_DIR_ENCTYPE_LAST,
> +};
>  
>  /* local variables */
>  
> @@ -150,6 +164,11 @@ static const unsigned char tpm_std_fatal
>  
>  static char dev_description[80];
>  
> +static struct enckey {
> +    uint8_t enctype;
> +    AES_KEY tpm_enc_key;
> +    AES_KEY tpm_dec_key;
> +} enckey;
>  
>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>                                                 enum BSEntryType be,
> @@ -264,7 +283,7 @@ static uint32_t tpm_builtin_calc_dir_che
>  
>  static bool tpm_builtin_is_valid_bsdir(BSDir *dir)
>  {
> -    if (dir->rev != BS_DIR_REV_CURRENT ||
> +    if (dir->rev > BS_DIR_REV_CURRENT ||
>          dir->num_entries > BS_DIR_MAX_NUM_ENTRIES) {
>          return false;
>      }
> @@ -295,6 +314,33 @@ static bool tpm_builtin_has_valid_conten
>      return rc;
>  }
>  
> +static bool tpm_builtin_supports_encryption(const BSDir *dir)
> +{
> +    return (dir->rev >= BS_DIR_REV2);
> +}
> +
> +
> +static bool tpm_builtin_has_missing_key(const BSDir *dir)
> +{
> +    return ((dir->enctype   != BS_DIR_ENCTYPE_NONE) &&
> +            (enckey.enctype == BS_DIR_ENCTYPE_NONE));
> +}
> +
> +
> +static bool tpm_builtin_has_unnecessary_key(const BSDir *dir)
> +{
> +    return (((dir->enctype   == BS_DIR_ENCTYPE_NONE) &&
> +             (enckey.enctype != BS_DIR_ENCTYPE_NONE)) ||
> +            ((!tpm_builtin_supports_encryption(dir)) &&
> +             (enckey.enctype != BS_DIR_ENCTYPE_NONE)));
> +}
> +
> +
> +static bool tpm_builtin_uses_unsupported_enctype(const BSDir *dir)
> +{
> +    return (dir->enctype >= BS_DIR_ENCTYPE_LAST);
> +}
> +
>  
>  static int tpm_builtin_create_blank_dir(BlockDriverState *bs)
>  {
> @@ -306,6 +352,7 @@ static int tpm_builtin_create_blank_dir(
>      dir = (BSDir *)buf;
>      dir->rev = BS_DIR_REV_CURRENT;
>      dir->num_entries = 0;
> +    dir->enctype = enckey.enctype;
>  
>      dir->checksum = tpm_builtin_calc_dir_checksum(dir);
>  
> @@ -407,6 +454,38 @@ static int tpm_builtin_startup_bs(BlockD
>  
>      tpm_builtin_dir_be_to_cpu(dir);
>  
> +    if (tpm_builtin_is_valid_bsdir(dir)) {
> +        if (tpm_builtin_supports_encryption(dir) &&
> +            tpm_builtin_has_missing_key(dir)) {
> +            fprintf(stderr,
> +                    "tpm: the data are encrypted but I am missing the key.\n");
> +            rc = -EIO;
> +            goto err_exit;
> +        }
> +        if (tpm_builtin_has_unnecessary_key(dir)) {
> +            fprintf(stderr,
> +                    "tpm: I have a key but the data are not encrypted.\n");
> +            rc = -EIO;
> +            goto err_exit;
> +        }
> +        if (tpm_builtin_supports_encryption(dir) &&
> +            tpm_builtin_uses_unsupported_enctype(dir)) {
> +            fprintf(stderr,
> +                    "tpm: State is encrypted with an unsupported encryption "
> +                    "scheme.\n");
> +            rc = -EIO;
> +            goto err_exit;
> +        }
> +        if (tpm_builtin_supports_encryption(dir) &&
> +            (dir->enctype != BS_DIR_ENCTYPE_NONE) &&
> +            !tpm_builtin_has_valid_content(dir)) {
> +            fprintf(stderr, "tpm: cannot read the data - "
> +                    "is this the wrong key?\n");
> +            rc = -EIO;
> +            goto err_exit;
> +        }
> +    }
> +
>      if (!tpm_builtin_is_valid_bsdir(dir) ||
>          !tpm_builtin_has_valid_content(dir)) {
>          /* if it's encrypted and has something else than null-content,
> @@ -569,6 +648,105 @@ static int set_bs_entry_size_crc(BlockDr
>  }
>  
>  
> +static int tpm_builtin_blocksize_roundup(uint8_t enctype, int plainsize)
> +{
> +    switch (enctype) {
> +    case BS_DIR_ENCTYPE_NONE:
> +        return plainsize;
> +    case BS_DIR_ENCTYPE_AES_CBC:
> +        return ALIGN(plainsize, AES_BLOCK_SIZE);
> +    default:
> +        assert(false);
> +        return 0;
> +    }
> +}
> +
> +
> +static int tpm_builtin_bdrv_pread(BlockDriverState *bs, int64_t offset,
> +                                  void *buf, int count,
> +                                  enum BSEntryType type)
> +{
> +    int ret;
> +    union {
> +        uint64_t ll[2];
> +        uint8_t b[16];
> +    } ivec;
> +    int toread = count;
> +
> +    toread = tpm_builtin_blocksize_roundup(enckey.enctype, count);
> +
> +    ret = bdrv_pread(bs, offset, buf, toread);
> +
> +    if (ret != toread) {
> +        return ret;
> +    }
> +
> +    switch (enckey.enctype) {
> +    case BS_DIR_ENCTYPE_NONE:
> +        break;
> +    case BS_DIR_ENCTYPE_AES_CBC:
> +        ivec.ll[0] = cpu_to_be64(type);
> +        ivec.ll[1] = 0;
> +
> +        AES_cbc_encrypt(buf, buf, toread, &enckey.tpm_dec_key, ivec.b, 0);
> +        break;
> +    default:
> +        assert(false);
> +    }
> +
> +    return count;
> +}
> +
> +
> +static int tpm_builtin_bdrv_pwrite(BlockDriverState *bs, int64_t offset,
> +                                   void *buf, int count,
> +                                   enum BSEntryType type)
> +{
> +    int ret;
> +    union {
> +        uint64_t ll[2];
> +        uint8_t b[16];
> +    } ivec;
> +    int towrite = count;
> +    void *out_buf = buf;
> +
> +    switch (enckey.enctype) {
> +    case BS_DIR_ENCTYPE_NONE:
> +        break;
> +    case BS_DIR_ENCTYPE_AES_CBC:
> +        ivec.ll[0] = cpu_to_be64(type);
> +        ivec.ll[1] = 0;
> +
> +        towrite = ALIGN(count, AES_BLOCK_SIZE);
> +
> +        if (towrite != count) {
> +            out_buf = g_malloc(towrite);
> +
> +            if (out_buf == NULL) {
> +                return -ENOMEM;
> +            }
> +        }
> +
> +        AES_cbc_encrypt(buf, out_buf, towrite, &enckey.tpm_enc_key, ivec.b, 1);
> +        break;
> +    default:
> +        assert(false);
> +    }
> +
> +    ret = bdrv_pwrite(bs, offset, out_buf, towrite);
> +
> +    if (out_buf != buf) {
> +        g_free(out_buf);
> +    }
> +
> +    if (ret == towrite) {
> +        return count;
> +    }
> +
> +    return ret;
> +}
> +
> +
>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>                                                 enum BSEntryType be,
>                                                 TPMSizedBuffer *tsb)
> @@ -594,7 +772,7 @@ static int tpm_builtin_load_sized_data_f
>          goto err_exit;
>      }
>  
> -    tsb->buffer = g_malloc(entry.blobsize);
> +    tsb->buffer = g_malloc(ALIGN(entry.blobsize, AES_BLOCK_SIZE));
>      if (!tsb->buffer) {
>          rc = -ENOMEM;
>          goto err_exit;
> @@ -602,7 +780,8 @@ static int tpm_builtin_load_sized_data_f
>  
>      tsb->size = entry.blobsize;
>  
> -    if (bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size) != tsb->size) {
> +    if (tpm_builtin_bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size, be) !=
> +        tsb->size) {
>          clear_sized_buffer(tsb);
>          fprintf(stderr, "tpm: Error while reading stored data!\n");
>          rc = -EIO;
> @@ -667,7 +846,8 @@ static int tpm_builtin_save_sized_data_t
>      }
>  
>      if (data_len > 0) {
> -        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len) {
> +        if (tpm_builtin_bdrv_pwrite(bs, entry.offset, data, data_len, be) !=
> +            data_len) {
>              rc = -EIO;
>          }
>      }
> @@ -1492,11 +1672,77 @@ static const char *tpm_builtin_create_de
>  }
>  
>  
> +/*
> + * Convert a string of hex digits to its binary representation.
> + * The conversion stops once either the maximum size of the binary
> + * array has been reached or an non-hex digit was encountered.
> + */

Don't we care about non-hex following a valid key?
This will silently discard them if length matches
a legal value by luck.

> +static size_t stream_to_bin(const char *stream,
> +                            unsigned char *bin, size_t bin_size)
> +{
> +    size_t c = 0;
> +    unsigned char nib = 0;
> +
> +    while (c < bin_size && stream[c] != 0) {
> +        if (stream[c] >= '0' && stream[c] <= '9') {
> +            nib |= stream[c] - '0';
> +        } else if (stream[c] >= 'A' && stream[c] <= 'F') {
> +            nib |= stream[c] - 'A' + 10;
> +        } else if (stream[c] >= 'a' && stream[c] <= 'f') {
> +            nib |= stream[c] - 'a' + 10;
> +        } else {
> +            break;
> +        }
> +
> +        if ((c & 1) == 1) {
> +            bin[c/2] = nib;
> +            nib = 0;
> +        } else {
> +            nib <<= 4;
> +            bin[c/2] = nib;
> +        }
> +
> +        c++;
> +    }
> +
> +    return c;
> +}

Can't this use something like scanf %x instead?
Something like the below seems to work for me,
and gives length in bytes and not nibbles.

#include <stdio.h>
#include <assert.h>

int main(int argc, char **argv)
{
        int l = 0, b = 0, s, n;
        char buf[256 / 8];
        for (b = 0; b < sizeof(buf); ++b) {
                s = sscanf(argv[1] + l, "%2hhx%n", buf + b, &n);
                if (s == 0) {
                        printf("invalid input. scanned %d bytes, text left: %s\n", b, argv[1] + l);
                        return 1;
                }
                assert(s != EOF && n >= 1 && n <= 2);
                l += n;
                if (!argv[1][l]) {
                        printf("scanned %d bytes length %d\n", b + 1, l);
                        return 0;
                }
        }
        printf("key too long. scanned %d bytes, text left: %s\n", b, argv[1] + l);
        return 2;
}



> +
> +

Two empty lines in a row :)

> +static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
> +                                        unsigned char *keyvalue,
> +                                        int *keysize)
> +{
> +    size_t c = 0;
> +
> +    /* skip over leading '0x' */
> +    if (!strncmp(rawkey, "0x", 2)) {
> +        rawkey += 2;
> +    }
> +
> +    c = stream_to_bin(rawkey, keyvalue, *keysize);
> +
> +    if (c == 256/4) {
> +        *keysize = 256;
> +    } else if (c >= 192/4) {
> +        *keysize = 192;
> +    } else if (c >= 128/4) {
> +        *keysize = 128;
> +    } else {
> +        return false;

Want to tell the user what went wrong?
Also, you don't allow skipping leading zeroes?

> +    }
> +
> +    return true;

Always put spaces around /.
But where does the /4 come from? 4 bits per character?


> +}
> +
> +
>  static TPMBackend *tpm_builtin_create(QemuOpts *opts, const char *id,
>                                        const char *model)
>  {
>      TPMBackend *driver;
>      const char *value;
> +    unsigned char keyvalue[256/8];
> +    int keysize = sizeof(keyvalue);
>  
>      driver = g_malloc(sizeof(TPMBackend));
>      if (!driver) {
> @@ -1523,6 +1769,33 @@ static TPMBackend *tpm_builtin_create(Qe
>          goto err_exit;
>      }
>  
> +    value = qemu_opt_get(opts, "key");
> +    if (value) {
> +        if (!strncasecmp(value, "aes-cbc:", 8)) {
> +            memset(keyvalue, 0x0, sizeof(keyvalue));
> +
> +            if (!tpm_builtin_parse_as_hexkey(&value[8], keyvalue, &keysize)) {
> +                keysize = 128;
> +                strncpy((char *)keyvalue, value, 128/8);
> +            }
> +
> +            if (AES_set_encrypt_key(keyvalue, keysize,
> +                                    &enckey.tpm_enc_key) != 0 ||
> +                AES_set_decrypt_key(keyvalue, keysize,
> +                                    &enckey.tpm_dec_key) != 0) {
> +                fprintf(stderr, "tpm: Error setting AES key.\n");
> +                goto err_exit;
> +            }
> +            enckey.enctype = BS_DIR_ENCTYPE_AES_CBC;
> +        } else {
> +            fprintf(stderr, "tpm: Unknown encryption scheme. Known types are: "
> +                            "aes-cbc.\n");
> +            goto err_exit;
> +        }
> +    } else {
> +        enckey.enctype = BS_DIR_ENCTYPE_NONE;
> +    }
> +
>      return driver;
>  
>  err_exit:
> Index: qemu-git/qemu-config.c
> ===================================================================
> --- qemu-git.orig/qemu-config.c
> +++ qemu-git/qemu-config.c
> @@ -522,6 +522,11 @@ static QemuOptsList qemu_tpmdev_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Persistent storage for TPM state",
>          },
> +        {
> +            .name = "key",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Data encryption key",
> +        },
>          { /* end of list */ }
>      },
>  };
> @@ -546,6 +551,11 @@ static QemuOptsList qemu_tpm_opts = {
>              .type = QEMU_OPT_STRING,
>              .help = "Persistent storage for TPM state",
>          },
> +        {
> +            .name = "key",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Data encryption key",
> +        },
>          { /* end of list */ }
>      },
>  };
> Index: qemu-git/tpm.c
> ===================================================================
> --- qemu-git.orig/tpm.c
> +++ qemu-git/tpm.c
> @@ -245,6 +245,7 @@ void tpm_cleanup(void)
>  void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
>  {
>      QemuOpts *opts;
> +    char *key;
>  
>      if (strcmp("none", optarg) != 0) {
>          if (*optarg == '?') {
> @@ -255,6 +256,15 @@ void tpm_config_parse(QemuOptsList *opts
>          if (!opts) {
>              exit(1);
>          }
> +
> +        /* if a key is provided, wipe it out so no one can see it with 'ps' */
> +        key = strstr(optarg, "key=");
> +        if (key) {
> +            key += 4;
> +            while (key[0] && key[0] != ',') {
> +                *key++ = '-';
> +            }
> +        }
>      }
>  }
>  
> Index: qemu-git/qemu-options.hx
> ===================================================================
> --- qemu-git.orig/qemu-options.hx
> +++ qemu-git/qemu-options.hx
> @@ -1766,8 +1766,9 @@ DEFHEADING(TPM device options:)
>  # ifdef CONFIG_TPM
>  DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>      "" \
> -    "-tpm builtin,path=<path>[,model=<model>]\n" \
> +    "-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 model=?    to list available TPM device models\n" \
>      "-tpm ?          to list available TPM backend types\n",
>      QEMU_ARCH_I386)
> @@ -1796,13 +1797,22 @@ Use ? to print all available TPM backend
>  qemu -tpmdev ?
>  @end example
>  
> -@item -tpmdev builtin ,id=@var{id}, path=@var{path}
> +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
>  
>  Creates an instance of the built-in TPM.
>  
>  @option{path} specifies the path to the QCoW2 image that will store
>  the TPM's persistent data. @option{path} is required.
>  
> +@option{key} specifies the AES key to use to encrypt the TPM's persistent
> +data. If encryption is to be used, the key must be provided the first
> +time a Qemu VM with attached TPM is started and the same key must subsequently
> +be used. The format of the key is the type of encryption to use, i.e.,
> +@code{aes-cbc}, followed by a colon and then the actual key. The key can
> +be a hex number with optional leading @code{0x}
> +and 32, 48 or 64 hex digits for 128, 192 or 256 bit AES keys respectively.
> +@option{key} is optional.
> +
>  To create a built-in TPM use the following two options:
>  @example
>  -tpmdev builtin,id=tpm0,path=<path_to_qcow2> -device tpm-tis,tpmdev=tpm0
> @@ -1810,12 +1820,18 @@ To create a built-in TPM use the followi
>  Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
>  @code{tpmdev=tpm0} in the device option.
>  
> +
> +To create a built-in TPM whose state is encrypted with a 128 bit AES key
> +using AES-CBC encryption scheme supply the following two options:
> +@example
> +-tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
> +@end example
>  @end table
>  
>  The short form of a TPM device option is:
>  @table @option
>  
> -@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}]
> +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}] [,key=@var{key}]
>  @findex -tpm
>  
>  @option{model} specifies the device model. The default device model is a
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-09-01 17:14   ` Michael S. Tsirkin
@ 2011-09-02  1:01     ` Stefan Berger
  2011-09-04 16:29       ` Michael S. Tsirkin
  2011-09-04 16:50       ` Michael S. Tsirkin
  0 siblings, 2 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:01 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:14 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:52AM -0400, Stefan Berger wrote:
>> This patch adds support for TPM command line options.
>> The command line supported here (considering the libtpms based
>> backend) are
>>
>> ./qemu-... -tpm builtin,path=<path to blockstorage file>
>>
>> and
>>
>> ./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
>>             -device tpm-tis,tpmdev=<id>
> do we really need both?
I had chatted with Anthony about this. I am following the existing 
pattern is use for example for -netdev / -net.

>> and
>>
>> ./qemu-... -tpmdev ?
>>
>> where the latter works similar to -soundhw ? and shows a list of
>> available TPM backends ('builtin').
>>
>> To show the available TPM models do:
>>
>> ./qemu-... -tpm model=?
> Can we live with -tpmdev for backend and plain device_add for frontend?
Can you give a more specific example? Is device_add a function call or a 
command line parameter in this context?
> Frontend would be connected to backend using a tpmdev matching the id
> of the frontend...

qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
          -device tpm-tis,tpmdev=<id>


Isn't that what I am doing?

>> In case of -tpm, 'type' (above 'builtin') and 'model' are interpreted in tpm.c.
>> In case of -tpmdev 'type' and 'id' are interpreted in tpm.c
>> Using the type parameter, the backend is chosen, i.e., 'builtin' for the
>> libtpms-based builtin TPM. The interpretation of the other parameters along
>> with determining whether enough parameters were provided is pushed into
>> the backend driver, which needs to implement the interface function
>> 'create' and return a TPMDriver structure if the VM can be started or 'NULL'
>> if not enough or bad parameters were provided.
>>
>> Since SeaBIOS will now use 128kb for ACPI tables the amount of reserved
>> memory for ACPI tables needs to be increased -- increasing it to 128kb.
> Increasing from which value to which?
 From 64kb to 128kb.
>> Monitor support for 'info tpm' has been added. It for example prints the
>> following:
>>
>> TPM devices:
>>    builtin: model=tpm-tis,id=tpm0
> This mixes frontend and backend properties.
>
There's currently only one frontend 'model' and that's the 'tpm-tis'. In 
case someone would want to write a virtio equivalent it would show the 
that the 'builtin' backend is connected to the 'virtio' frontend model. 
If above is not correct, how should it look like?
>> v8:
>>   - adjusting formatting of backend drivers output to accomodate better
>>     formatting of 'passthrough' backend output
>>
>> v6:
>>   - use #idef CONFIG_TPM to surround TPM calls
>>   - use QLIST_FOREACH_SAFE rather than QLIST_FOREACH in tpm_cleanup
>>   - commented backend ops in tpm.h
>>   - moving to IRQ 5 (11 collided with network cards)
>>
>> v5:
>>   - fixing typo reported by Serge Hallyn
>>   - Adapting code to split command line parameters supporting
>>     -tpmdev ... -device tpm-tis,tpmdev=...
>>   - moved code out of arch_init.c|h into tpm.c|h
>>   - increasing reserved memory for ACPI tables to 128kb (from 64kb)
>>   - the backend interface has a create() function for interpreting the command
>>     line parameters and returning a TPMDevice structure; previoulsy
>>     this function was called handle_options()
>>   - the backend interface has a destroy() function for cleaning up after
>>     the create() function was called
>>   - added support for 'info tpm' in monitor
>>
>> v4:
>>   - coding style fixes
>>
>> v3:
>>   - added hw/tpm_tis.h to this patch so Qemu compiles at this stage
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
>>
>> ---
>>   Makefile.target |    1
>>   hmp-commands.hx |    2
>>   hw/pc.c         |    7 +
>>   hw/tpm_tis.h    |   75 +++++++++++++++
>>   monitor.c       |   10 ++
>>   qemu-config.c   |   46 +++++++++
>>   qemu-options.hx |   80 ++++++++++++++++
>>   tpm.c           |  279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   tpm.h           |  112 ++++++++++++++++++++++
>>   vl.c            |   18 +++
>>   10 files changed, 629 insertions(+), 1 deletion(-)
>>
>> Index: qemu-git/qemu-options.hx
>> ===================================================================
>> --- qemu-git.orig/qemu-options.hx
>> +++ qemu-git/qemu-options.hx
>> @@ -1760,6 +1760,86 @@ ETEXI
>>
>>   DEFHEADING()
>>
>> +DEFHEADING(TPM device options:)
>> +
>> +#ifndef _WIN32
>> +# ifdef CONFIG_TPM
>> +DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>> +    "" \
>> +    "-tpm builtin,path=<path>[,model=<model>]\n" \
>> +    "                enable a builtin TPM with state in file in path\n" \
>> +    "-tpm model=?    to list available TPM device models\n" \
>> +    "-tpm ?          to list available TPM backend types\n",
>> +    QEMU_ARCH_I386)
>> +DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
>> +    "-tpmdev [builtin],id=str[,option][,option][,...]\n",
>> +    QEMU_ARCH_I386)
>> +# endif
>> +#endif
>> +STEXI
>> +
>> +The general form of a TPM device option is:
>> +@table @option
>> +
>> +@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
>> +@findex -tpmdev
>> +Backend type must be:
>> +@option{builtin}.
>> +
>> +The specific backend type will determine the applicable options.
>> +The @code{-tpmdev} options requires a @code{-device} option.
>> +
>> +Options to each backend are described below.
>> +
>> +Use ? to print all available TPM backend types.
>> +@example
>> +qemu -tpmdev ?
>> +@end example
>> +
>> +@item -tpmdev builtin ,id=@var{id}, path=@var{path}
>> +
>> +Creates an instance of the built-in TPM.
>> +
>> +@option{path} specifies the path to the QCoW2 image that will store
>> +the TPM's persistent data. @option{path} is required.
>> +
>> +To create a built-in TPM use the following two options:
>> +@example
>> +-tpmdev builtin,id=tpm0,path=<path_to_qcow2>  -device tpm-tis,tpmdev=tpm0
>> +@end example
>> +Not that the @code{-tpmdev} id is @code{tpm0} and is referenced by
>> +@code{tpmdev=tpm0} in the device option.
>> +
>> +@end table
>> +
>> +The short form of a TPM device option is:
>> +@table @option
>> +
>> +@item -tpm @var{backend-type}, path=@var{path} [,model=@var{model}]
>> +@findex -tpm
>> +
>> +@option{model} specifies the device model. The default device model is a
>> +@code{tpm-tis} device model. @code{model} is optional.
>> +
>> +Use ? to print all available TPM models.
>> +@example
>> +qemu -tpm model=?
>> +@end example
>> +
>> +The other options have the same meaning as explained above.
>> +
>> +To create a built-in TPM use the following option:
>> +@example
>> +-tpm builtin, path=<path_to_qcow2>
>> +@end example
>> +
>> +@end table
>> +
>> +ETEXI
>> +
>> +
>> +DEFHEADING()
>> +
>>   DEFHEADING(Linux/Multiboot boot specific:)
>>   STEXI
>>
>> Index: qemu-git/vl.c
>> ===================================================================
>> --- qemu-git.orig/vl.c
>> +++ qemu-git/vl.c
>> @@ -137,6 +137,7 @@ int main(int argc, char **argv)
>>   #include "block.h"
>>   #include "blockdev.h"
>>   #include "block-migration.h"
>> +#include "tpm.h"
>>   #include "dma.h"
>>   #include "audio/audio.h"
>>   #include "migration.h"
>> @@ -2498,6 +2499,14 @@ int main(int argc, char **argv, char **e
>>                   ram_size = value;
>>                   break;
>>               }
>> +#ifdef CONFIG_TPM
>> +            case QEMU_OPTION_tpm:
>> +                tpm_config_parse(qemu_find_opts("tpm"), optarg);
>> +                break;
>> +            case QEMU_OPTION_tpmdev:
>> +                tpm_config_parse(qemu_find_opts("tpmdev"), optarg);
>> +                break;
>> +#endif
>>               case QEMU_OPTION_mempath:
>>                   mem_path = optarg;
>>                   break;
>> @@ -3149,6 +3158,12 @@ int main(int argc, char **argv, char **e
>>           exit(1);
>>       }
>>
>> +#ifdef CONFIG_TPM
>> +    if (tpm_init()<  0) {
>> +        exit(1);
>> +    }
>> +#endif
>> +
>>       /* init the bluetooth world */
>>       if (foreach_device_config(DEV_BT, bt_parse))
>>           exit(1);
>> @@ -3394,6 +3409,9 @@ int main(int argc, char **argv, char **e
>>       quit_timers();
>>       net_cleanup();
>>       res_free();
>> +#ifdef CONFIG_TPM
>> +    tpm_cleanup();
>> +#endif
>>
>>       return 0;
>>   }
>> Index: qemu-git/qemu-config.c
>> ===================================================================
>> --- qemu-git.orig/qemu-config.c
>> +++ qemu-git/qemu-config.c
>> @@ -507,6 +507,50 @@ QemuOptsList qemu_boot_opts = {
>>       },
>>   };
>>
>> +static QemuOptsList qemu_tpmdev_opts = {
>> +    .name = "tpmdev",
>> +    .implied_opt_name = "type",
>> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head),
>> +    .desc = {
>> +        {
>> +            .name = "type",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "Type of TPM backend",
>> +        },
>> +        {
>> +            .name = "path",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "Persistent storage for TPM state",
>> +        },
>> +        { /* end of list */ }
>> +    },
>> +};
>> +
>> +static QemuOptsList qemu_tpm_opts = {
>> +    .name = "tpm",
>> +    .implied_opt_name = "type",
>> +    .head = QTAILQ_HEAD_INITIALIZER(qemu_tpm_opts.head),
>> +    .desc = {
>> +        {
>> +            .name = "type",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "Type of TPM backend",
>> +        },
>> +        {
>> +            .name = "model",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "Model of TPM frontend",
>> +        },
>> +        {
>> +            .name = "path",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "Persistent storage for TPM state",
>> +        },
>> +        { /* end of list */ }
>> +    },
>> +};
>> +
>> +
>>   static QemuOptsList *vm_config_groups[32] = {
>>       &qemu_drive_opts,
>>       &qemu_chardev_opts,
>> @@ -523,6 +567,8 @@ static QemuOptsList *vm_config_groups[32
>>       &qemu_option_rom_opts,
>>       &qemu_machine_opts,
>>       &qemu_boot_opts,
>> +&qemu_tpmdev_opts,
>> +&qemu_tpm_opts,
>>       NULL,
>>   };
>>
>> Index: qemu-git/hw/tpm_tis.h
>> ===================================================================
>> --- /dev/null
>> +++ qemu-git/hw/tpm_tis.h
>> @@ -0,0 +1,75 @@
>> +/*
>> + * tpm_tis.h - include file for tpm_tis.c
>> + *
>> + * Copyright (C) 2006,2010,2011 IBM Corporation
>> + *
>> + * Author: Stefan Berger<stefanb@us.ibm.com>
>> + *         David Safford<safford@us.ibm.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation, version 2 of the
>> + * License.
>> + */
>> +#ifndef _HW_TPM_TIS_H
>> +#define _HW_TPM_TIS_H
>> +
>> +#include "isa.h"
>> +#include "block_int.h"
>> +#include "qemu-thread.h"
>> +
>> +#include<stdint.h>
>> +
>> +#define TIS_ADDR_BASE       0xFED40000
>> +
>> +#define NUM_LOCALITIES      5     /* per spec */
>> +#define NO_LOCALITY         0xff
> Please use consistent prefixes to avoid namespace
> pollution. E.g. tpm_tis_ for stuff in tpm_tis.h, etc.
>
>
Ok. I'll change the functions. Also the #define's ?

[...]
>> +
>> +static int configure_tpm(QemuOpts *opts, int is_tpmdev)
>> +{
>> +    const char *value;
>> +    const char *id = TPM_DEFAULT_DEVICE_ID;
>> +    const char *model =  NULL;
>> +    const TPMDriverOps *be;
>> +    TPMBackend *drv;
>> +
>> +    if (!QLIST_EMPTY(&tpm_backends)) {
>> +        fprintf(stderr, "Only one TPM is allowed.\n");
>> +        return 1;
>> +    }
>> +
>> +    if (is_tpmdev) {
>> +        id = qemu_opts_id(opts);
>> +        if (id == NULL) {
>> +            qerror_report(QERR_MISSING_PARAMETER, "id");
>> +            return 1;
>> +        }
>> +    } else {
>> +        model = qemu_opt_get(opts, "model");
>> +        if (model) {
>> +            if (strcmp(model, "?") == 0) {
>> +                tpm_display_models(stdout);
>> +                return 1;
>> +            }
>> +            if (!tpm_check_model(model)) {
>> +                qerror_report(QERR_INVALID_PARAMETER_VALUE, "model",
>> +                              "a tpm model");
>> +                tpm_display_models(stderr);
>> +                return 1;
>> +            }
>> +        } else {
>> +            model = TPM_DEFAULT_DEVICE_MODEL;
>> +        }
>> +    }
>> +
>> +    value = qemu_opt_get(opts, "type");
>> +    if (!value) {
>> +        qerror_report(QERR_MISSING_PARAMETER, "type");
>> +        tpm_display_backend_drivers(stderr);
>> +        return 1;
>> +    }
>> +
>> +    be = tpm_get_backend_driver(value);
>> +    if (be == NULL) {
>> +        qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
>> +                      "a tpm backend type");
>> +        tpm_display_backend_drivers(stderr);
>> +        return 1;
>> +    }
>> +
>> +    assert((is_tpmdev&&  model == NULL) || (!is_tpmdev&&  model != NULL));
> Why isn't this using qdev for parameter passing?
>
Can you point me to a device that is using qdev for parameter passing. 
Also this part is very similar to how the networking works (net.c).

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-09-01 18:14   ` Michael S. Tsirkin
@ 2011-09-02  1:02     ` Stefan Berger
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:02 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 02:14 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:52AM -0400, Stefan Berger wrote:
>> This patch adds support for TPM command line options.
>> The command line supported here (considering the libtpms based
>> backend) are
>>
>> ./qemu-... -tpm builtin,path=<path to blockstorage file>
>>
>> and
>>
>> ./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
>>             -device tpm-tis,tpmdev=<id>
>>
> builtin is a weird name in fact. Rename to libtpms?
Anyone have a particular opinion? If I renamed it, should I rename the 
files as well along with the function names that now all start with 
tpm_builtin_?

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-09-01 17:20   ` Michael S. Tsirkin
@ 2011-09-02  1:12     ` Stefan Berger
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:12 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:20 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:54AM -0400, Stefan Berger wrote:
>> This patch adds support for handling of persistent state to the TPM TIS
>> frontend.
>>
>> The currently used buffer is determined (can only be in currently active
>> locality and either be a read or a write buffer) and only that buffer's content
>> is stored. The reverse is done when the state is restored from disk
>> where the buffer's content are copied into the currently used buffer.
>>
>> To keep compatibility with existing Xen implementation the VMStateDescription
>> was adapted to be compatible with existing state. For that I am adding Andreas
>> Niederl as an author to the file.
>>
>> v5:
>>   - removing qdev.no_user=1
>>
>> v4:
>>   - main thread releases the 'state' lock while periodically calling the
>>     backends function that may request it to write data into block storage.
>>
>> v3:
>>   - all functions prefixed with tis_
>>   - while the main thread is waiting for an outstanding TPM command to finish,
>>     it periodically does some work (writes data to the block storage)
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
>>
>> ---
>>   hw/tpm_tis.c |  166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 166 insertions(+)
>>
>> Index: qemu-git/hw/tpm_tis.c
>> ===================================================================
>> --- qemu-git.orig/hw/tpm_tis.c
>> +++ qemu-git/hw/tpm_tis.c
>> @@ -6,6 +6,8 @@
>>    * Author: Stefan Berger<stefanb@us.ibm.com>
>>    *         David Safford<safford@us.ibm.com>
>>    *
>> + * Xen 4 support: Andrease Niederl<andreas.niederl@iaik.tugraz.at>
>> + *
>>    * This program is free software; you can redistribute it and/or
>>    * modify it under the terms of the GNU General Public License as
>>    * published by the Free Software Foundation, version 2 of the
>> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
>>    err_exit:
>>       return -1;
>>   }
>> +
>> +/* persistent state handling */
>> +
>> +static void tis_pre_save(void *opaque)
>> +{
>> +    TPMState *s = opaque;
>> +    uint8_t locty = s->active_locty;
>> +
>> +    qemu_mutex_lock(&s->state_lock);
>> +
>> +    /* wait for outstanding requests to complete */
>> +    if (IS_VALID_LOCTY(locty)&&  s->loc[locty].state == STATE_EXECUTION) {
>> +        if (!s->be_driver->ops->job_for_main_thread) {
>> +            qemu_cond_wait(&s->from_tpm_cond,&s->state_lock);
>> +        } else {
>> +            while (s->loc[locty].state == STATE_EXECUTION) {
>> +                qemu_mutex_unlock(&s->state_lock);
>> +
>> +                s->be_driver->ops->job_for_main_thread(NULL);
>> +                usleep(10000);
>> +
>> +                qemu_mutex_lock(&s->state_lock);
>> +            }
>> +        }
>> +    }
>> +
>> +#ifdef DEBUG_TIS_SR
>> +    fprintf(stderr,
>> +            "tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = %d\n",
>> +            s->loc[0].r_offset, s->loc[0].w_offset);
>> +    if (s->loc[0].r_offset) {
>> +        tis_dump_state(opaque, 0);
>> +    }
>> +#endif
>> +
>> +    qemu_mutex_unlock(&s->state_lock);
>> +
>> +    /* copy current active read or write buffer into the buffer
>> +       written to disk */
>> +    if (IS_VALID_LOCTY(locty)) {
>> +        switch (s->loc[locty].state) {
>> +        case STATE_RECEPTION:
>> +            memcpy(s->buf,
>> +                   s->loc[locty].w_buffer.buffer,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].w_buffer.size));
>> +            s->offset = s->loc[locty].w_offset;
>> +        break;
>> +        case STATE_COMPLETION:
>> +            memcpy(s->buf,
>> +                   s->loc[locty].r_buffer.buffer,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].r_buffer.size));
>> +            s->offset = s->loc[locty].r_offset;
>> +        break;
>> +        default:
>> +            /* leak nothing */
>> +            memset(s->buf, 0x0, sizeof(s->buf));
>> +        break;
>> +        }
>> +    }
>> +
>> +    s->be_driver->ops->save_volatile_data();
>> +}
>> +
>> +
>> +static int tis_post_load(void *opaque,
>> +                         int version_id __attribute__((unused)))
>> +{
>> +    TPMState *s = opaque;
>> +
>> +    uint8_t locty = s->active_locty;
>> +
>> +    if (IS_VALID_LOCTY(locty)) {
>> +        switch (s->loc[locty].state) {
>> +        case STATE_RECEPTION:
>> +            memcpy(s->loc[locty].w_buffer.buffer,
>> +                   s->buf,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].w_buffer.size));
>> +            s->loc[locty].w_offset = s->offset;
>> +        break;
>> +        case STATE_COMPLETION:
>> +            memcpy(s->loc[locty].r_buffer.buffer,
>> +                   s->buf,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].r_buffer.size));
>> +            s->loc[locty].r_offset = s->offset;
>> +        break;
>> +        default:
>> +        break;
>> +        }
>> +    }
> Should this do something with interrupts as well?
Even if the last action the TIS emulator was doing before the VM 
suspended completely was to reveive the last outstanding response then 
the tis_raise_irq() function was called in tis_tpm_receive_cb()  and 
along with that qemu_irq_raise(s->irq) was executed. Presumably it's not 
necessary to raise this same IRQ again immediately after resuming but 
this IRQ was restored as part of restoring the interrupt device model's 
state and the OS (still) sees the IRQ as pending.


>> +
>> +#ifdef DEBUG_TIS_SR
>> +    fprintf(stderr,
>> +            "tpm_tis: resume : locty 0 : r_offset = %d, w_offset = %d\n",
>> +            s->loc[0].r_offset, s->loc[0].w_offset);
>> +#endif
>> +
>> +    return s->be_driver->ops->load_volatile_data(s);
>> +}
>> +
>> +
>> +static const VMStateDescription vmstate_locty = {
>> +    .name = "loc",
>> +    .version_id = 1,
>> +    .minimum_version_id = 0,
>> +    .minimum_version_id_old = 0,
>> +    .fields      = (VMStateField[]) {
>> +        VMSTATE_UINT32(state, TPMLocality),
>> +        VMSTATE_UINT32(inte, TPMLocality),
>> +        VMSTATE_UINT32(ints, TPMLocality),
>> +        VMSTATE_UINT8(access, TPMLocality),
>> +        VMSTATE_UINT8(sts, TPMLocality),
>> +        VMSTATE_END_OF_LIST(),
>> +    }
>> +};
>> +
>> +
>> +static const VMStateDescription vmstate_tis = {
>> +    .name = "tpm",
>> +    .version_id = 1,
>> +    .minimum_version_id = 0,
>> +    .minimum_version_id_old = 0,
>> +    .pre_save  = tis_pre_save,
>> +    .post_load = tis_post_load,
>> +    .fields = (VMStateField[]) {
>> +        VMSTATE_UINT32(irq_num, TPMState),
>> +        VMSTATE_UINT32(offset, TPMState),
>> +        VMSTATE_BUFFER(buf, TPMState),
>> +        VMSTATE_UINT8(active_locty, TPMState),
>> +        VMSTATE_UINT8(aborting_locty, TPMState),
>> +        VMSTATE_UINT8(next_locty, TPMState),
> Is irq_num guest modifiable?
It's hard-wired to IRQ 5.
> If yes post load should do something with it?
> If not, why are we migrating it?
True. I'll remove it from the migrated state.
>> +
>> +        VMSTATE_STRUCT_ARRAY(loc, TPMState, NUM_LOCALITIES, 1,
>> +                             vmstate_locty, TPMLocality),
>> +
>> +        VMSTATE_END_OF_LIST()
>> +    }
>> +};
>> +
>> +
>> +static ISADeviceInfo tis_device_info = {
>> +    .init         = tis_init,
>> +    .qdev.name    = "tpm-tis",
>> +    .qdev.size    = sizeof(TPMState),
>> +    .qdev.vmsd    =&vmstate_tis,
>> +    .qdev.reset   = tis_reset,
>> +    .qdev.props = (Property[]) {
>> +        DEFINE_PROP_UINT32("irq", TPMState,
>> +                           irq_num, TPM_TIS_IRQ),
>> +        DEFINE_PROP_STRING("tpmdev", TPMState, backend),
>> +        DEFINE_PROP_END_OF_LIST(),
>> +    },
>> +};
>> +
>> +
>> +static void tis_register_device(void)
>> +{
>> +    isa_qdev_register(&tis_device_info);
>> +}
>> +
>> +device_init(tis_register_device)
>>
> So this is a qdev device. Why do we need a new flag to set it up then?
>
>
Which flag are you referring to?

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process
  2011-09-01 17:23   ` Michael S. Tsirkin
@ 2011-09-02  1:16     ` Stefan Berger
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:16 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:23 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:55AM -0400, Stefan Berger wrote:
>> The TPM interface (tpm_tis) needs to be explicitly enabled via
>> ./configure --enable-tpm. This patch also restricts the building of the
>> TPM support to i386 and x86_64 targets since only there it is currently
>> supported. This prevents that one will end up with support for a frontend
>> but no available backend.
> This can happen anyway - just don't load the tpms driver :)
> Presumably if libtpms exists on the system, we should
> assume it's there for a reason. configure should test
> that IMO and not limit architectures artificially.
Well, what above means is that one a ppc machine you can still build a 
qemu-system-x86_64 with tpm 'builtin' (libtpms) support if libtpms is 
found on the system. However, I am not building the arm, ppc, mips etc. 
emulators with TPM support simply because I only have code in hw/pc.c 
enabling a x86_64/i386 pc with a TPM. If someone wants to extend and 
test those other machines then this restriction would have to be opened 
up to those targets that they extended.

    Stefan

>> v3:
>>   - fixed and moved hunks in Makefile.target into right place
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
>> Index:qemu/Makefile.target
>> ===================================================================
>> ---
>>   Makefile.target |    1 +
>>   configure       |   20 ++++++++++++++++++++
>>   2 files changed, 21 insertions(+)
>>
>> Index: qemu-git/Makefile.target
>> ===================================================================
>> --- qemu-git.orig/Makefile.target
>> +++ qemu-git/Makefile.target
>> @@ -233,6 +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
>>
>>   # shared objects
>>   obj-ppc-y = ppc.o
>> Index: qemu-git/configure
>> ===================================================================
>> --- qemu-git.orig/configure
>> +++ qemu-git/configure
>> @@ -183,6 +183,7 @@ usb_redir=""
>>   opengl=""
>>   zlib="yes"
>>   guest_agent="yes"
>> +tpm="no"
>>
>>   # parse CC options first
>>   for opt do
>> @@ -765,6 +766,8 @@ for opt do
>>     ;;
>>     --disable-guest-agent) guest_agent="no"
>>     ;;
>> +  --enable-tpm) tpm="yes"
>> +  ;;
>>     *) echo "ERROR: unknown option $opt"; show_help="yes"
>>     ;;
>>     esac
>> @@ -1044,6 +1047,7 @@ echo "  --disable-usb-redir      disable
>>   echo "  --enable-usb-redir       enable usb network redirection support"
>>   echo "  --disable-guest-agent    disable building of the QEMU Guest Agent"
>>   echo "  --enable-guest-agent     enable building of the QEMU Guest Agent"
>> +echo "  --enable-tpm             enable an emulated TPM"
>>   echo ""
>>   echo "NOTE: The object files are built at the place where configure is launched"
>>   exit 1
>> @@ -2731,6 +2735,7 @@ echo "nss used          $smartcard_nss"
>>   echo "usb net redir     $usb_redir"
>>   echo "OpenGL support    $opengl"
>>   echo "build guest agent $guest_agent"
>> +echo "TPM support       $tpm"
>>
>>   if test "$sdl_too_old" = "yes"; then
>>   echo "->  Your SDL version is too old - please upgrade to have SDL support"
>> @@ -3555,6 +3560,21 @@ if test "$gprof" = "yes" ; then
>>     fi
>>   fi
>>
>> +if test "$tpm" = "yes"; then
>> +  has_tpm=0
>> +  if test "$target_softmmu" = "yes" ; then
>> +    case "$TARGET_BASE_ARCH" in
>> +    i386)
>> +      has_tpm=1
>> +    ;;
>> +    esac
>> +  fi
>> +
>> +  if test "$has_tpm" = "1"; then
>> +      echo "CONFIG_TPM=y">>  $config_host_mak
>> +  fi
>> +fi
>> +
>>   linker_script="-Wl,-T../config-host.ld -Wl,-T,\$(SRC_PATH)/\$(ARCH).ld"
>>   if test "$target_linux_user" = "yes" -o "$target_bsd_user" = "yes" ; then
>>     case "$ARCH" in
>>

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend
  2011-09-01 17:27   ` Michael S. Tsirkin
@ 2011-09-02  1:24     ` Stefan Berger
  2011-09-04 16:27       ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:24 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:27 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:58AM -0400, Stefan Berger wrote:
>> 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 connects 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 is registered with
>> libtpms. Those callback functions are invoked by libtpms for example to
>> store the TPM's state.
>>
>> 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.
>>
>> v6:
>>    - cache a copy of the last permanent state blob
>>    - move some functions into tpm_builtin.h
>>    - 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.
>>    - add out_len variable used in the thread
>>
>> v5:
>>    - check access() to TPM's state file and report error if file is not
>>      accessible
>>
>> 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<stefanb@linux.vnet.ibm.com>
>>
>> ---
>>   configure        |    1
>>   hw/tpm_builtin.c |  450 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
>>   hw/tpm_builtin.h |   56 ++++++
>>   3 files changed, 482 insertions(+), 25 deletions(-)
>>
>> 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
> Just wondering - might a stub driver be useful for
> basic testing on systems without TPM hardware?
'systems without TPM hardware' -- we're not relying on underlying TPM 
provided by the host. But I assume that's not what you meant.

A 'null' driver, which responds to every command with an error response, 
is added in patch 13.

> The namespace comment applies to this and all other patches.
>
In patch 6 I am adding a skeleton backend driver that I am transforming 
into the libtpms-based backend in patch 7. I didn't name the file 
tpm_skeleton.c but already tpm_builtin.c and all functions already start 
with the prefix tpm_builtin. This presumably makes it easier to review 
since the 'meat' is added in part 7 and unnecessary function name 
changes are avoided. The null driver later on and the passthrough driver 
(posted by Andreas Niederl) all can be derived rather easily from that 
initial skeleton driver. In those we do adhere to the namespace requirement.

   Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-01 17:32   ` Michael S. Tsirkin
@ 2011-09-02  1:53     ` Stefan Berger
  2011-09-04 19:32       ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  1:53 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:32 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:59AM -0400, Stefan Berger wrote:
>> This patch introduces file locking via fcntl() for the block layer so that
>> concurrent access to files shared by 2 Qemu instances, for example via NFS,
>> can be serialized. This feature is useful primarily during initial phases of
>> VM migration where the target machine's TIS driver validates the block
>> storage (and in a later patch checks for missing AES keys) and terminates
>> Qemu if the storage is found to be faulty. This then allows migration to
>> be gracefully terminated and Qemu continues running on the source machine.
>>
>> Support for win32 is based on win32 API and has been lightly tested with a
>> standalone test program locking shared storage from two different machines.
>>
>> To enable locking a file multiple times, a counter is used. Actual locking
>> happens the very first time and unlocking happens when the counter is zero.
>>
>> v7:
>>   - fixed compilation error in win32 part
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
> Generally, what all other devices do is perform validation
> as the last step in migration when device state
> is restored. On failure, management can decide what to do:
> retry migration or restart on source.
>
> Why is TPM special and needs to be treated differently?
>
>
>
Some background on the TPM: Typically a TPM is a chip with built-in 
NVRAM where it can write its persistent state into. We are simulating 
this NVRAM through a file (QCoW2).

What's special about the TPM is that Qemu stores the libtpms-internal 
state onto that QCoW2 file whereas normally it's the OS that stores its 
data onto files accessed through BlockDriverState. This in turn is 
related to the fact that the TPM does not only have the internal state 
of the TPM TIS frontend but also that of the libtpms backend where for 
example keys and the owner's password are stored and have to be written 
into persistent storage.

More detail: Typically one starts out with an empty QCoW2 file created 
via qemu-img. Once Qemu starts and initializes the libtpms-based TPM, it 
tries to read existing state from that QCoW2 file. Since there is no 
state stored in the QCoW2, the TPM will start initializing itself to an 
initial 'blank' state. Then once the user has booted into the OS he can 
use the TPM. Assuming that user now creates an Endorsement Key  (EK) 
using a command sent to the TPM, then this EK becomes part of the 
persistent state of the TPM and the persistent state is written into the 
QCoW2 file. The next time the VM is (cold) started that EK has to be 
there. Assuming now the user takes ownership of the TPM, then his 
password(s) also becomes part of the persistent state of the TPM and has 
to be written into the QCoW2 file. Again, the fact that ownership was 
taken leads to the requirement that the passwords are expected to be 
there upon another (cold) restart of the VM. All this is covered by 
storing the TPM's persistent state into that file.

I hope this explains what is special about the TPM.

   Stefan
>> ---
>>
>> ---
>>   block.c           |   41 +++++++++++++++++++++++++++++++++++
>>   block.h           |    8 ++++++
>>   block/raw-posix.c |   63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   block/raw-win32.c |   52 ++++++++++++++++++++++++++++++++++++++++++++
>>   block_int.h       |    4 +++
>>   5 files changed, 168 insertions(+)
>>
>> Index: qemu-git/block.c
>> ===================================================================
>> --- qemu-git.orig/block.c
>> +++ qemu-git/block.c
>> @@ -521,6 +521,8 @@ static int bdrv_open_common(BlockDriverS
>>           goto free_and_fail;
>>       }
>>
>> +    drv->num_locks = 0;
>> +
>>       bs->keep_read_only = bs->read_only = !(open_flags&  BDRV_O_RDWR);
>>
>>       ret = refresh_total_sectors(bs, bs->total_sectors);
>> @@ -1316,6 +1318,45 @@ void bdrv_get_geometry(BlockDriverState
>>       *nb_sectors_ptr = length;
>>   }
>>
>> +/* file locking */
>> +static int bdrv_lock_common(BlockDriverState *bs, BDRVLockType lock_type)
>> +{
>> +    BlockDriver *drv = bs->drv;
>> +
>> +    if (!drv) {
>> +        return -ENOMEDIUM;
>> +    }
>> +
>> +    if (bs->file) {
>> +        drv = bs->file->drv;
>> +        if (drv->bdrv_lock) {
>> +            return drv->bdrv_lock(bs->file, lock_type);
>> +        }
>> +    }
>> +
>> +    if (drv->bdrv_lock) {
>> +        return drv->bdrv_lock(bs, lock_type);
>> +    }
>> +
>> +    return -ENOTSUP;
>> +}
>> +
>> +
>> +int bdrv_lock(BlockDriverState *bs)
>> +{
>> +    if (bdrv_is_read_only(bs)) {
>> +        return bdrv_lock_common(bs, BDRV_F_RDLCK);
>> +    }
>> +
>> +    return bdrv_lock_common(bs, BDRV_F_WRLCK);
>> +}
>> +
>> +void bdrv_unlock(BlockDriverState *bs)
>> +{
>> +    bdrv_lock_common(bs, BDRV_F_UNLCK);
>> +}
>> +
>> +
>>   struct partition {
>>           uint8_t boot_ind;           /* 0x80 - active */
>>           uint8_t head;               /* starting head */
>> Index: qemu-git/block.h
>> ===================================================================
>> --- qemu-git.orig/block.h
>> +++ qemu-git/block.h
>> @@ -43,6 +43,12 @@ typedef struct QEMUSnapshotInfo {
>>   #define BDRV_SECTOR_MASK   ~(BDRV_SECTOR_SIZE - 1)
>>
>>   typedef enum {
>> +    BDRV_F_UNLCK,
>> +    BDRV_F_RDLCK,
>> +    BDRV_F_WRLCK,
>> +} BDRVLockType;
>> +
>> +typedef enum {
>>       BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC,
>>       BLOCK_ERR_STOP_ANY
>>   } BlockErrorAction;
>> @@ -100,6 +106,8 @@ int bdrv_commit(BlockDriverState *bs);
>>   void bdrv_commit_all(void);
>>   int bdrv_change_backing_file(BlockDriverState *bs,
>>       const char *backing_file, const char *backing_fmt);
>> +int bdrv_lock(BlockDriverState *bs);
>> +void bdrv_unlock(BlockDriverState *bs);
>>   void bdrv_register(BlockDriver *bdrv);
>>
>>
>> Index: qemu-git/block/raw-posix.c
>> ===================================================================
>> --- qemu-git.orig/block/raw-posix.c
>> +++ qemu-git/block/raw-posix.c
>> @@ -803,6 +803,67 @@ static int64_t raw_get_allocated_file_si
>>       return (int64_t)st.st_blocks * 512;
>>   }
>>
>> +static int raw_lock(BlockDriverState *bs, BDRVLockType lock_type)
>> +{
>> +    BlockDriver *drv = bs->drv;
>> +    BDRVRawState *s = bs->opaque;
>> +    struct flock flock = {
>> +        .l_whence = SEEK_SET,
>> +        .l_start = 0,
>> +        .l_len = 0,
>> +    };
>> +    int n;
>> +
>> +    switch (lock_type) {
>> +    case BDRV_F_RDLCK:
>> +    case BDRV_F_WRLCK:
>> +        if (drv->num_locks) {
>> +            drv->num_locks++;
>> +            return 0;
>> +        }
>> +        flock.l_type = (lock_type == BDRV_F_RDLCK) ? F_RDLCK : F_WRLCK;
>> +        break;
>> +
>> +    case BDRV_F_UNLCK:
>> +        if (--drv->num_locks>  0) {
>> +            return 0;
>> +        }
>> +
>> +        assert(drv->num_locks == 0);
>> +
>> +        flock.l_type = F_UNLCK;
>> +        break;
>> +
>> +    default:
>> +        return -EINVAL;
>> +    }
>> +
>> +    while (1) {
>> +        n = fcntl(s->fd, F_SETLKW,&flock);
>> +        if (n<  0) {
>> +            if (errno == EINTR) {
>> +                continue;
>> +            }
>> +            if (errno == EAGAIN) {
>> +                usleep(10000);
>> +                continue;
>> +            }
>> +        }
>> +        break;
>> +    }
>> +
>> +    if (n == 0&&
>> +        ((lock_type == BDRV_F_RDLCK) || (lock_type == BDRV_F_WRLCK))) {
>> +        drv->num_locks = 1;
>> +    }
>> +
>> +    if (n) {
>> +        return -errno;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>>   static int raw_create(const char *filename, QEMUOptionParameter *options)
>>   {
>>       int fd;
>> @@ -901,6 +962,8 @@ static BlockDriver bdrv_file = {
>>       .bdrv_get_allocated_file_size
>>                           = raw_get_allocated_file_size,
>>
>> +    .bdrv_lock = raw_lock,
>> +
>>       .create_options = raw_create_options,
>>   };
>>
>> Index: qemu-git/block_int.h
>> ===================================================================
>> --- qemu-git.orig/block_int.h
>> +++ qemu-git/block_int.h
>> @@ -146,6 +146,10 @@ struct BlockDriver {
>>        */
>>       int (*bdrv_has_zero_init)(BlockDriverState *bs);
>>
>> +    /* File locking */
>> +    int num_locks;
>> +    int (*bdrv_lock)(BlockDriverState *bs, BDRVLockType lock_type);
>> +
>>       QLIST_ENTRY(BlockDriver) list;
>>   };
>>
>> Index: qemu-git/block/raw-win32.c
>> ===================================================================
>> --- qemu-git.orig/block/raw-win32.c
>> +++ qemu-git/block/raw-win32.c
>> @@ -242,6 +242,57 @@ static int64_t raw_get_allocated_file_si
>>       return st.st_size;
>>   }
>>
>> +static int raw_lock(BlockDriverState *bs, int lock_type)
>> +{
>> +    BlockDriver *drv = bs->drv;
>> +    BDRVRawState *s = bs->opaque;
>> +    OVERLAPPED ov;
>> +    BOOL res;
>> +    DWORD num_bytes;
>> +
>> +    switch (lock_type) {
>> +    case BDRV_F_RDLCK:
>> +    case BDRV_F_WRLCK:
>> +        if (drv->num_locks) {
>> +            drv->num_locks++;
>> +            return 0;
>> +        }
>> +
>> +        memset(&ov, 0, sizeof(ov));
>> +
>> +        res = LockFileEx(s->hfile, LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, ~0,&ov);
>> +
>> +        if (res == FALSE) {
>> +            res = GetOverlappedResult(s->hfile,&ov,&num_bytes, TRUE);
>> +        }
>> +
>> +        if (res == TRUE) {
>> +            drv->num_locks = 1;
>> +        }
>> +
>> +        break;
>> +
>> +    case BDRV_F_UNLCK:
>> +        if (--drv->num_locks>  0) {
>> +            return 0;
>> +        }
>> +
>> +        assert(drv->num_locks>= 0);
>> +
>> +        res = UnlockFile(s->hfile, 0, 0, ~0, ~0);
>> +        break;
>> +
>> +    default:
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (res == FALSE) {
>> +        return -EIO;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>>   static int raw_create(const char *filename, QEMUOptionParameter *options)
>>   {
>>       int fd;
>> @@ -289,6 +340,7 @@ static BlockDriver bdrv_file = {
>>       .bdrv_get_allocated_file_size
>>                           = raw_get_allocated_file_size,
>>
>> +    .bdrv_lock		= raw_lock,
>>       .create_options = raw_create_options,
>>   };
>>
>>

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-01 19:26   ` Michael S. Tsirkin
@ 2011-09-02  2:23     ` Stefan Berger
  2011-09-04 16:58       ` Michael S. Tsirkin
  2011-09-07 18:55       ` Michael S. Tsirkin
  0 siblings, 2 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  2:23 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 03:26 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:36:01AM -0400, Stefan Berger wrote:
>> This patch adds encryption of the individual state blobs that are written
>> into the block storage. The 'directory' at the beginnig of the block
>> storage is not encrypted.
> Does this mean that there's a new format that we store data
> in, and that qemu needs to support?
No, this type of encryption is completely handled inside this file.
> If so, this needs an entry under docs documenting the format.
>
>> The encryption support added in this patch would also work if QCoW2 was not
>> to be used as the (only) image file format to store the TPM's state.
> What does 'was not to be used' above mean?
>
The only image file format that the libtpms based 'builtin' backend 
currently accepts is the QCoW2 file format. QCoW2 provides encryption of 
its own (I find it a bit problematic). From what I know none of the 
other file formats provides encryption, i.e., 'raw' for sure does not. 
So if the image type format requirement was to be relaxed to also allow 
for example 'raw', then the encryption added in this patch would still work.
>> Keys can be passed as a string of hexadecimal digits forming a 256, 192 or
>> 128 bit AES key. The string can optionally start with '0x'. If the
>> parser does not recognize it as a hexadecimal number, the string itself is
>> taken as the AES key, which makes for example 'my_key' a valid AES key
>> parameter. It is also necessary to provide the encryption scheme.
>> Currently only 'aes-cbc' is supported.  An example for a valid key command
>> line argument is:
>>
>> -tpm builtin,key=aes-cbc:0x1234567890abcdef123456
>>
>> The key passed via command line argument is wiped from the command
>> line after parsing. If for example key=aes-cbc:0x1234... was passed it will
>> then be changed to key=------... so that 'ps' does not show the key anymore.
>> Obviously it cannot be completely prevented that the key is visible during a
>> very short period of time until qemu gets to the point where the code wiping
>> the key is reached.
>>
>> A byte indicating the encryption type being used is introduced in the
>> directory structure indicating whether blobs are encrypted and if so, what
>> encryption type was used, i.e., aes-cbc.
>>
>> An additional 'layer' for reading and writing the blobs to the underlying
>> block storage is added. This layer encrypts the blobs for writing if a key is
>> available. Similarly it decrypts the blobs after reading.
>>
>> Checks are added that test
>> - whether encryption is supported follwing the revision of the directory
>>    structure (rev>= 2)
> You never generate rev 1 code, right?
I did this in the previous patch that implemented rev 1 that knew 
nothing about the encryption added in rev 2.
> So why keep that support around in code?
> The first version merged into qemu should be revision 0 (or 1, as you like).
I chose '1'. See patch 9:

+#define BS_DIR_REV1         1
+
+#define BS_DIR_REV_CURRENT  BS_DIR_REV1
+

So I think it's the proper thing to do to increase the revision number 
from 1 to 2 since it's in two separate patches (even if they were to be 
applied immediately).
> Don't support legacy with old version of your patch.
>
>> - whether a key has been provided although all data are stored in clear-text
>> - whether a key is missing for decryption.
>>
>> In either one of the cases the backend reports an error message to the user
>> and Qemu terminates.
>>
>> -v7:
>>    - cleaned up function parsing key
>>
>> -v6:
>>    - changed the format of the key= to take the type of encryption into
>>      account: key=aes-cbc:0x12345... and reworked code for encryption and
>>      decryption of blobs;
> separate type and data:
> keytype=aes-cbc,key=0x123  to avoid introducing more option parsing.
> Also, are people likely to have the key in a file?
> If yes maybe read a key from there and skip parsing completely?
>
I think both choices should probably exist. Now what's a good file 
format? Would we expect to find a hex number in there or should it 
always be assumed to be a binary file?
>>    - modified directory entry to hold a uint_8 describing the encryption
>>      type (none, aes-cbc) being used for the blobs.
>>    - incrementing revision of the directory to '2' indicating encryption
>>      support
>>
>> -v5:
>>    - -tpmdev now also gets a key parameter
>>    - add documentation about key parameter
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
>>
>> ---
>>   hw/tpm_builtin.c |  285 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
>>   qemu-config.c    |   10 +
>>   qemu-options.hx  |   22 +++-
>>   tpm.c            |   10 +
>>   4 files changed, 318 insertions(+), 9 deletions(-)
>>
>> Index: qemu-git/hw/tpm_builtin.c
>> ===================================================================
>> --- qemu-git.orig/hw/tpm_builtin.c
>> +++ qemu-git/hw/tpm_builtin.c
>> @@ -27,6 +27,7 @@
>>   #include "hw/pc.h"
>>   #include "migration.h"
>>   #include "sysemu.h"
>> +#include "aes.h"
>>
>>   #include<libtpms/tpm_library.h>
>>   #include<libtpms/tpm_error.h>
>> @@ -110,14 +111,27 @@ typedef struct BSDir {
>>       uint16_t  rev;
>>       uint32_t  checksum;
>>       uint32_t  num_entries;
>> -    uint32_t  reserved[10];
>> +    uint8_t   enctype;
>> +    uint8_t   reserved1[3];
>> +    uint32_t  reserved[8];
>>       BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
>>   } __attribute__((packed)) BSDir;
>>
>>
>>   #define BS_DIR_REV1         1
>> +/* rev 2 added encryption */
>> +#define BS_DIR_REV2         2
>>
>> -#define BS_DIR_REV_CURRENT  BS_DIR_REV1
>> +
>> +#define BS_DIR_REV_CURRENT  BS_DIR_REV2
>> +
>> +/* above enctype */
>> +enum BSEnctype {
>> +    BS_DIR_ENCTYPE_NONE = 0,
>> +    BS_DIR_ENCTYPE_AES_CBC,
>> +
>> +    BS_DIR_ENCTYPE_LAST,
>> +};
>>
>>   /* local variables */
>>
>> @@ -150,6 +164,11 @@ static const unsigned char tpm_std_fatal
>>
>>   static char dev_description[80];
>>
>> +static struct enckey {
>> +    uint8_t enctype;
>> +    AES_KEY tpm_enc_key;
>> +    AES_KEY tpm_dec_key;
>> +} enckey;
>>
>>   static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>>                                                  enum BSEntryType be,
>> @@ -264,7 +283,7 @@ static uint32_t tpm_builtin_calc_dir_che
>>
>>   static bool tpm_builtin_is_valid_bsdir(BSDir *dir)
>>   {
>> -    if (dir->rev != BS_DIR_REV_CURRENT ||
>> +    if (dir->rev>  BS_DIR_REV_CURRENT ||
>>           dir->num_entries>  BS_DIR_MAX_NUM_ENTRIES) {
>>           return false;
>>       }
>> @@ -295,6 +314,33 @@ static bool tpm_builtin_has_valid_conten
>>       return rc;
>>   }
>>
>> +static bool tpm_builtin_supports_encryption(const BSDir *dir)
>> +{
>> +    return (dir->rev>= BS_DIR_REV2);
>> +}
>> +
>> +
>> +static bool tpm_builtin_has_missing_key(const BSDir *dir)
>> +{
>> +    return ((dir->enctype   != BS_DIR_ENCTYPE_NONE)&&
>> +            (enckey.enctype == BS_DIR_ENCTYPE_NONE));
>> +}
>> +
>> +
>> +static bool tpm_builtin_has_unnecessary_key(const BSDir *dir)
>> +{
>> +    return (((dir->enctype   == BS_DIR_ENCTYPE_NONE)&&
>> +             (enckey.enctype != BS_DIR_ENCTYPE_NONE)) ||
>> +            ((!tpm_builtin_supports_encryption(dir))&&
>> +             (enckey.enctype != BS_DIR_ENCTYPE_NONE)));
>> +}
>> +
>> +
>> +static bool tpm_builtin_uses_unsupported_enctype(const BSDir *dir)
>> +{
>> +    return (dir->enctype>= BS_DIR_ENCTYPE_LAST);
>> +}
>> +
>>
>>   static int tpm_builtin_create_blank_dir(BlockDriverState *bs)
>>   {
>> @@ -306,6 +352,7 @@ static int tpm_builtin_create_blank_dir(
>>       dir = (BSDir *)buf;
>>       dir->rev = BS_DIR_REV_CURRENT;
>>       dir->num_entries = 0;
>> +    dir->enctype = enckey.enctype;
>>
>>       dir->checksum = tpm_builtin_calc_dir_checksum(dir);
>>
>> @@ -407,6 +454,38 @@ static int tpm_builtin_startup_bs(BlockD
>>
>>       tpm_builtin_dir_be_to_cpu(dir);
>>
>> +    if (tpm_builtin_is_valid_bsdir(dir)) {
>> +        if (tpm_builtin_supports_encryption(dir)&&
>> +            tpm_builtin_has_missing_key(dir)) {
>> +            fprintf(stderr,
>> +                    "tpm: the data are encrypted but I am missing the key.\n");
>> +            rc = -EIO;
>> +            goto err_exit;
>> +        }
>> +        if (tpm_builtin_has_unnecessary_key(dir)) {
>> +            fprintf(stderr,
>> +                    "tpm: I have a key but the data are not encrypted.\n");
>> +            rc = -EIO;
>> +            goto err_exit;
>> +        }
>> +        if (tpm_builtin_supports_encryption(dir)&&
>> +            tpm_builtin_uses_unsupported_enctype(dir)) {
>> +            fprintf(stderr,
>> +                    "tpm: State is encrypted with an unsupported encryption "
>> +                    "scheme.\n");
>> +            rc = -EIO;
>> +            goto err_exit;
>> +        }
>> +        if (tpm_builtin_supports_encryption(dir)&&
>> +            (dir->enctype != BS_DIR_ENCTYPE_NONE)&&
>> +            !tpm_builtin_has_valid_content(dir)) {
>> +            fprintf(stderr, "tpm: cannot read the data - "
>> +                    "is this the wrong key?\n");
>> +            rc = -EIO;
>> +            goto err_exit;
>> +        }
>> +    }
>> +
>>       if (!tpm_builtin_is_valid_bsdir(dir) ||
>>           !tpm_builtin_has_valid_content(dir)) {
>>           /* if it's encrypted and has something else than null-content,
>> @@ -569,6 +648,105 @@ static int set_bs_entry_size_crc(BlockDr
>>   }
>>
>>
>> +static int tpm_builtin_blocksize_roundup(uint8_t enctype, int plainsize)
>> +{
>> +    switch (enctype) {
>> +    case BS_DIR_ENCTYPE_NONE:
>> +        return plainsize;
>> +    case BS_DIR_ENCTYPE_AES_CBC:
>> +        return ALIGN(plainsize, AES_BLOCK_SIZE);
>> +    default:
>> +        assert(false);
>> +        return 0;
>> +    }
>> +}
>> +
>> +
>> +static int tpm_builtin_bdrv_pread(BlockDriverState *bs, int64_t offset,
>> +                                  void *buf, int count,
>> +                                  enum BSEntryType type)
>> +{
>> +    int ret;
>> +    union {
>> +        uint64_t ll[2];
>> +        uint8_t b[16];
>> +    } ivec;
>> +    int toread = count;
>> +
>> +    toread = tpm_builtin_blocksize_roundup(enckey.enctype, count);
>> +
>> +    ret = bdrv_pread(bs, offset, buf, toread);
>> +
>> +    if (ret != toread) {
>> +        return ret;
>> +    }
>> +
>> +    switch (enckey.enctype) {
>> +    case BS_DIR_ENCTYPE_NONE:
>> +        break;
>> +    case BS_DIR_ENCTYPE_AES_CBC:
>> +        ivec.ll[0] = cpu_to_be64(type);
>> +        ivec.ll[1] = 0;
>> +
>> +        AES_cbc_encrypt(buf, buf, toread,&enckey.tpm_dec_key, ivec.b, 0);
>> +        break;
>> +    default:
>> +        assert(false);
>> +    }
>> +
>> +    return count;
>> +}
>> +
>> +
>> +static int tpm_builtin_bdrv_pwrite(BlockDriverState *bs, int64_t offset,
>> +                                   void *buf, int count,
>> +                                   enum BSEntryType type)
>> +{
>> +    int ret;
>> +    union {
>> +        uint64_t ll[2];
>> +        uint8_t b[16];
>> +    } ivec;
>> +    int towrite = count;
>> +    void *out_buf = buf;
>> +
>> +    switch (enckey.enctype) {
>> +    case BS_DIR_ENCTYPE_NONE:
>> +        break;
>> +    case BS_DIR_ENCTYPE_AES_CBC:
>> +        ivec.ll[0] = cpu_to_be64(type);
>> +        ivec.ll[1] = 0;
>> +
>> +        towrite = ALIGN(count, AES_BLOCK_SIZE);
>> +
>> +        if (towrite != count) {
>> +            out_buf = g_malloc(towrite);
>> +
>> +            if (out_buf == NULL) {
>> +                return -ENOMEM;
>> +            }
>> +        }
>> +
>> +        AES_cbc_encrypt(buf, out_buf, towrite,&enckey.tpm_enc_key, ivec.b, 1);
>> +        break;
>> +    default:
>> +        assert(false);
>> +    }
>> +
>> +    ret = bdrv_pwrite(bs, offset, out_buf, towrite);
>> +
>> +    if (out_buf != buf) {
>> +        g_free(out_buf);
>> +    }
>> +
>> +    if (ret == towrite) {
>> +        return count;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +
>>   static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>>                                                  enum BSEntryType be,
>>                                                  TPMSizedBuffer *tsb)
>> @@ -594,7 +772,7 @@ static int tpm_builtin_load_sized_data_f
>>           goto err_exit;
>>       }
>>
>> -    tsb->buffer = g_malloc(entry.blobsize);
>> +    tsb->buffer = g_malloc(ALIGN(entry.blobsize, AES_BLOCK_SIZE));
>>       if (!tsb->buffer) {
>>           rc = -ENOMEM;
>>           goto err_exit;
>> @@ -602,7 +780,8 @@ static int tpm_builtin_load_sized_data_f
>>
>>       tsb->size = entry.blobsize;
>>
>> -    if (bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size) != tsb->size) {
>> +    if (tpm_builtin_bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size, be) !=
>> +        tsb->size) {
>>           clear_sized_buffer(tsb);
>>           fprintf(stderr, "tpm: Error while reading stored data!\n");
>>           rc = -EIO;
>> @@ -667,7 +846,8 @@ static int tpm_builtin_save_sized_data_t
>>       }
>>
>>       if (data_len>  0) {
>> -        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len) {
>> +        if (tpm_builtin_bdrv_pwrite(bs, entry.offset, data, data_len, be) !=
>> +            data_len) {
>>               rc = -EIO;
>>           }
>>       }
>> @@ -1492,11 +1672,77 @@ static const char *tpm_builtin_create_de
>>   }
>>
>>
>> +/*
>> + * Convert a string of hex digits to its binary representation.
>> + * The conversion stops once either the maximum size of the binary
>> + * array has been reached or an non-hex digit was encountered.
>> + */
> Don't we care about non-hex following a valid key?
Do you have an example? This function is meant to convert 0x1234567 to a 
binary stream. An equivalent would be 0x1234567X, since it would 
terminate parsing on 'X'.
> This will silently discard them if length matches
> a legal value by luck.
>
>> +static size_t stream_to_bin(const char *stream,
>> +                            unsigned char *bin, size_t bin_size)
>> +{
>> +    size_t c = 0;
>> +    unsigned char nib = 0;
>> +
>> +    while (c<  bin_size&&  stream[c] != 0) {
>> +        if (stream[c]>= '0'&&  stream[c]<= '9') {
>> +            nib |= stream[c] - '0';
>> +        } else if (stream[c]>= 'A'&&  stream[c]<= 'F') {
>> +            nib |= stream[c] - 'A' + 10;
>> +        } else if (stream[c]>= 'a'&&  stream[c]<= 'f') {
>> +            nib |= stream[c] - 'a' + 10;
>> +        } else {
>> +            break;
>> +        }
>> +
>> +        if ((c&  1) == 1) {
>> +            bin[c/2] = nib;
>> +            nib = 0;
>> +        } else {
>> +            nib<<= 4;
>> +            bin[c/2] = nib;
>> +        }
>> +
>> +        c++;
>> +    }
>> +
>> +    return c;
>> +}
> Can't this use something like scanf %x instead?
Sure it could...

> Something like the below seems to work for me,
> and gives length in bytes and not nibbles.
>
> #include<stdio.h>
> #include<assert.h>
>
> int main(int argc, char **argv)
> {
>          int l = 0, b = 0, s, n;
>          char buf[256 / 8];
>          for (b = 0; b<  sizeof(buf); ++b) {
>                  s = sscanf(argv[1] + l, "%2hhx%n", buf + b,&n);
>                  if (s == 0) {
>                          printf("invalid input. scanned %d bytes, text left: %s\n", b, argv[1] + l);
>                          return 1;
>                  }
>                  assert(s != EOF&&  n>= 1&&  n<= 2);
>                  l += n;
>                  if (!argv[1][l]) {
>                          printf("scanned %d bytes length %d\n", b + 1, l);
>                          return 0;
>                  }
>          }
>          printf("key too long. scanned %d bytes, text left: %s\n", b, argv[1] + l);
>          return 2;
> }
>
>
>
>> +
>> +
> Two empty lines in a row :)
>
Probably this is not the only occurrence... Is this a problem?
>> +static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
>> +                                        unsigned char *keyvalue,
>> +                                        int *keysize)
>> +{
>> +    size_t c = 0;
>> +
>> +    /* skip over leading '0x' */
>> +    if (!strncmp(rawkey, "0x", 2)) {
>> +        rawkey += 2;
>> +    }
>> +
>> +    c = stream_to_bin(rawkey, keyvalue, *keysize);
>> +
>> +    if (c == 256/4) {
>> +        *keysize = 256;
>> +    } else if (c>= 192/4) {
>> +        *keysize = 192;
>> +    } else if (c>= 128/4) {
>> +        *keysize = 128;
>> +    } else {
>> +        return false;
> Want to tell the user what went wrong?
Here's what the key parser handles:
- all keys >= 256 bits are truncated to 256 bits
- all keys >= 192 bits are truncated to 192 bits
- all keys >= 128 bits are truncated to 128 bits
- all keys < 128 bits are assumed to not be given as a hexadecimal 
number but the string itself is the key, i.e. 'HELLOWORLD' becomes a 
valid key.
> Also, you don't allow skipping leading zeroes?
An AES key should be allowed to have leading zeros, no?
>> +    }
>> +
>> +    return true;
> Always put spaces around /.
> But where does the /4 come from? 4 bits per character?
>
c is the number of 'nibbles'. 4 bits in a nibble - that's what the /4 
comes from.
>> +}
>> +
>> +
>>   static TPMBackend *tpm_builtin_create(QemuOpts *opts, const char *id,
>>                                         const char *model)
>>   {
>>       TPMBackend *driver;
>>       const char *value;
>> +    unsigned char keyvalue[256/8];
>> +    int keysize = sizeof(keyvalue);
>>
>>       driver = g_malloc(sizeof(TPMBackend));
>>       if (!driver) {
>> @@ -1523,6 +1769,33 @@ static TPMBackend *tpm_builtin_create(Qe
>>           goto err_exit;
>>       }
>>
>> +    value = qemu_opt_get(opts, "key");
>> +    if (value) {
>> +        if (!strncasecmp(value, "aes-cbc:", 8)) {
>> +            memset(keyvalue, 0x0, sizeof(keyvalue));
>> +
>> +            if (!tpm_builtin_parse_as_hexkey(&value[8], keyvalue,&keysize)) {
>> +                keysize = 128;
>> +                strncpy((char *)keyvalue, value, 128/8);
Here first the input is attempted to be parsed as hex key and if that 
fails the input string is taken as the key. It should be &value[8] here 
-- so that's a bug.

   Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation
  2011-09-01 17:40   ` Michael S. Tsirkin
@ 2011-09-02  2:41     ` Stefan Berger
  2011-09-04 16:42       ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  2:41 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 01:40 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:36:04AM -0400, Stefan Berger wrote:
>> 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.
> Why limit this to intel platforms then?
>
Same as with my previous comment: only hw/pc.c handles the TPM device 
for x86_64 and i386 emulators.
>> v8:
>>   - initializing 'in' variable
>>
>> Signed-off-by: Stefan Berger<stefanb@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
> Is this the same one an earlier patch removed?
It's similar, yes, but previously it served to cut the 'builtin' 
backend's code size into two smaller patches.
>> + *
>> + *  Copyright (c) 2010, 2011 IBM Corporation
>> + *  Copyright (c) 2010, 2011 Stefan Berger
> Why dual copyright btw?
>
Not a specific reason...
>> + *
>> + * 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 */
> don't use C++ comments please.
I ran the perl script over the patches and it pointed that out to me. I 
only kept it because I found similar comments for DEBUG #defines in 
other files.
>> +
>> +
>> +/* 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;
> this lacks consistency in naming.
> prefix everything with tpm_null?
>
Ok.

>> +
>> +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];
> Use symbolic constants above?
>
In this case dev_description could be a constant.

static char tpm_null_dev_description[] = "Null TPM backend driver";
>> +
>> +
>> +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
> Hmm, so now we remove some code added earlier?
Yes. I added this for the purpose of bisectability but also logically 
previously the --enable-tpm configure parameter caused failure in case 
libtpms was not found to be installed. Now it doesn't fail anymore since 
at least the null driver doesn't have any dependencies and  can always 
be built.
> Pls don't do this for new code: this makes review harder, not easier.
> It makes sense for old code to ensure bisectability.
So how should I change it? Should I not add this failure notice in the 
first place? How should the build configure script then handle the 
--enable-tpm if it cannot find libtpms and thus cannot fulfill the 
--enable-tpm?

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-01 18:10   ` Michael S. Tsirkin
  2011-09-01 19:01     ` Michael S. Tsirkin
@ 2011-09-02  3:00     ` Stefan Berger
  2011-09-04 16:38       ` Michael S. Tsirkin
  1 sibling, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  3:00 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 02:10 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:36:05AM -0400, Stefan Berger wrote:
>> This patch adds a -tpm ...,initstate=...,... command line option to the
>> TPM's existing options and enables the TPM to be initialized with an
>> existing state blob. This in turn allows us to simulate TPM manufacturing
>> and equip the TPM with an endorsement key, certificates and initialize its
>> NVRAM areas etc.. This step is typically done during manufacturng of the TPM
>> and/or the (physical) machine.
>>
>> The initial state can be passed either as file or via a file descriptor. The
>> encoding of the state can either be binary or in form of a base64-encoded
>> blob surrounded by tags indicating the start and end.
>>
>> The intial state can be produced through a yet-to-be-published tpm-authoring
>> tool.
>>
>> Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
> I am guessing we get the base64 format from tpmlib?
The tpm-authoring tool uses a libtpms-based TPM to create this initial 
state.

In libvirt I (would like to) have the possibility to execute an external 
script as part of the start of the VM that can create this initial 
state. The output of that program is pipe()ed into Qemu via a 
filedescriptor, where libvirt sets up the pipe and passed one end to the 
program and the other to Qemu. This external script can display other 
information but the data between the begin and end marker of the 
base64-encode state are critical and are finally digested by this patch.
>> ---
>>   hw/tpm_builtin.c |  123 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
>>   qemu-config.c    |   12 +++++
>>   qemu-options.hx  |   40 ++++++++++++++++-
>>   3 files changed, 172 insertions(+), 3 deletions(-)
>>
>> Index: qemu-git/hw/tpm_builtin.c
>> ===================================================================
>> --- qemu-git.orig/hw/tpm_builtin.c
>> +++ qemu-git/hw/tpm_builtin.c
>> @@ -170,9 +170,16 @@ static struct enckey {
>>       AES_KEY tpm_dec_key;
>>   } enckey;
>>
>> +static int tpm_initstatefd = -1;
>> +static bool tpm_initstate_bin;
>> +
> This hardcodes assumption of a single backend.
>
Yes, there's typically a single TPM in a system.
>>   static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
>>                                                  enum BSEntryType be,
>>                                                  TPMSizedBuffer *tsb);
>> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
>> +                                                uint32_t *length,
>> +                                                size_t tpm_number,
>> +                                                const char *name);
>>
>>
>>
>> @@ -1269,7 +1276,7 @@ static TPM_RESULT tpm_builtin_nvram_load
>>           *length = permanent_state.size;
>>
>>           if (*length == 0) {
>> -            rc = TPM_RETRY;
>> +            rc = tpm_builtin_get_initial_state(data, length, tpm_number, name);
>>           } else {
>>               /* keep a copy of the last permanent state */
>>               rc = TPM_Malloc(data, *length);
>> @@ -1452,6 +1459,94 @@ static TPM_RESULT tpm_builtin_io_getphys
>>   }
>>
>>
>> +static TPM_RESULT tpm_builtin_get_initial_state(unsigned char **data,
>> +                                                uint32_t *length,
>> +                                                size_t tpm_number,
>> +                                                const char *name)
>> +{
>> +    TPM_RESULT rc = TPM_RETRY;
>> +    uint32_t allocated = 0;
>> +    int len, flags;
>> +    unsigned char buf[1024];
>> +    unsigned char *result = NULL;
>> +    size_t result_len;
>> +
>> +    if (tpm_initstatefd>= 0) {
>> +        *data = NULL;
>> +        *length = 0;
>> +
>> +        flags = fcntl(tpm_initstatefd, F_GETFL);
>> +        if (flags<  0 ||
>> +            fcntl(tpm_initstatefd, F_SETFL, flags&  ~O_NONBLOCK)<  0) {
>> +            return TPM_FAIL;
>> +        }
>> +
>> +        while (TRUE) {
>> +            len = read(tpm_initstatefd, buf, sizeof(buf));
>> +
>> +            if (len>  0) {
>> +                if (len>  allocated - *length) {
>> +                    allocated = *length + len + 1024;
>> +                    if (TPM_Realloc(data, allocated) != TPM_SUCCESS) {
>> +                        goto err_exit;
>> +                    }
>> +                }
>> +                memcpy(&(*data)[*length], buf, len);
>> +                *length += len;
>> +                (*data)[*length] = 0;
>> +            } else if (len == 0) {
>> +                rc = TPM_SUCCESS;
>> +                break;
>> +            } else if (len<  0) {
>> +                if (errno == EINTR) {
>> +                    continue;
>> +                }
>> +                goto err_exit;
>> +            }
>> +        }
>> +
>> +        if (*data == NULL) {
>> +            /* nothing read */
>> +            rc = TPM_FAIL;
>> +            goto err_exit;
>> +        }
>> +
>> +        if (!tpm_initstate_bin) {
>> +            if (TPMLIB_DecodeBlob((char *)*data, TPMLIB_BLOB_TYPE_INITSTATE,
>> +&result,&result_len) != TPM_SUCCESS) {
>> +                goto err_exit;
>> +            }
>> +            TPM_Free(*data);
>> +            *data = result;
>> +            *length = result_len;
>> +            result = NULL;
>> +        }
>> +        /* sanity check for the size of the blob */
>> +        if (*length>  tpmlib_get_prop(TPMPROP_TPM_MAX_NV_SPACE)) {
>> +            goto err_exit;
>> +        }
> Do we really have to hand-craft file reading?
> How large is TPMPROP_TPM_MAX_NV_SPACE?
> If not too large, we can just allocate that
> and do a single fread call?
Yes, I could do that, too.
> Or, we rely on glib now - can we use
> g_io_channel_read_to_end () or something like that?
>
>
GIOStatus   g_io_channel_read_to_end        (GIOChannel *channel,
                                              gchar **str_return,
                                              gsize *length,
                                              GError **error);

I'd rather not use it. Presumably it uses g_malloc() to internally 
allocate the str_return. However, the buffer we are allocating in this 
function is given to the libtpms, which in turn will call plain free() 
on it. I don't want this to cause a problem if not g_free() is called on 
this buffer.

>> +        /* have it written into the BlockStorage */
>> +        rc = tpm_builtin_nvram_storedata(*data, *length, tpm_number, name);
> What if that backend is compiled-out?
> link will fail?
No, the called function is in the same file.
>> +        if (rc != TPM_SUCCESS) {
>> +            goto err_exit;
>> +        }
>> +    }
>> +
>> +norm_exit:
>> +    close(tpm_initstatefd);
>> +    tpm_initstatefd = -1;
>> +
>> +    return rc;
>> +
>> +err_exit:
>> +    TPM_Free(*data);
>> +    *data = NULL;
>> +    *length = 0;
>> +    TPM_Free(result);
>> +
>> +    goto norm_exit;
>> +}
>> +
>>   /*****************************************************************/
>>
>>
>> @@ -1748,6 +1843,7 @@ static TPMBackend *tpm_builtin_create(Qe
>>       const char *value;
>>       unsigned char keyvalue[256/8];
>>       int keysize = sizeof(keyvalue);
>> +    unsigned int offset;
>>
>>       driver = g_malloc(sizeof(TPMBackend));
>>       if (!driver) {
>> @@ -1801,6 +1897,28 @@ static TPMBackend *tpm_builtin_create(Qe
>>           enckey.enctype = BS_DIR_ENCTYPE_NONE;
>>       }
>>
>> +    value = qemu_opt_get(opts, "initstate");
>> +    if (value) {
>> +        offset = 0;
>> +
>> +        if (!strncmp(value, "bin:", 4)) {
>> +            tpm_initstate_bin = true;
>> +            offset = 4;
>> +        } else if (!strncmp(value, "base64:", 7)) {
>> +            tpm_initstate_bin = false;
>> +            offset = 7;
>> +        }
>> +
>> +        if (sscanf(&value[offset], "fd:%d",&tpm_initstatefd) != 1) {
>> +            tpm_initstatefd = open(&value[offset], O_RDONLY);
>> +            if (tpm_initstatefd<  0) {
>> +                fprintf(stderr, "tpm: could not open file '%s' for reading.\n",
>> +                        value);
>> +                goto err_exit;
>> +            }
>> +        }
>> +    }
>> +
> Separate options for fd and for file mode would be better.
>
initstate_fd=base64:<file descriptor>
initstate_fd=bin:<file descriptor>
initstate=base64:<path to file>
initstate=bin:<path to file>

Along these lines?

>>       return driver;
>>
>>   err_exit:
>> @@ -1816,6 +1934,9 @@ static void tpm_builtin_destroy(TPMBacke
>>       g_free(driver->id);
>>       g_free(driver->model);
>>       g_free(driver);
>> +
>> +    close(tpm_initstatefd);
>> +    tpm_initstatefd = -1;
>>   }
>>
>>
>> Index: qemu-git/qemu-config.c
>> ===================================================================
>> --- qemu-git.orig/qemu-config.c
>> +++ qemu-git/qemu-config.c
>> @@ -527,6 +527,12 @@ static QemuOptsList qemu_tpmdev_opts = {
>>               .type = QEMU_OPT_STRING,
>>               .help = "Data encryption key",
>>           },
>> +        {
>> +            .name = "initstate",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "File or file descriptor for reading initial TPM state "
>> +                    "from",
>> +        },
>>           { /* end of list */ }
>>       },
>>   };
>> @@ -556,6 +562,12 @@ static QemuOptsList qemu_tpm_opts = {
>>               .type = QEMU_OPT_STRING,
>>               .help = "Data encryption key",
>>           },
>> +        {
>> +            .name = "initstate",
>> +            .type = QEMU_OPT_STRING,
>> +            .help = "File or file descriptor for reading initial TPM state "
>> +                    "from",
>> +        },
>>           { /* end of list */ }
>>       },
>>   };
> I think description should document the magic bin:/base64: etc strings,
> or better get rid of them.
>
I would like to keep both formats...
>> Index: qemu-git/qemu-options.hx
>> ===================================================================
>> --- qemu-git.orig/qemu-options.hx
>> +++ qemu-git/qemu-options.hx
>> @@ -1767,8 +1767,10 @@ DEFHEADING(TPM device options:)
>>   DEF("tpm", HAS_ARG, QEMU_OPTION_tpm, \
>>       "" \
>>       "-tpm builtin,path=<path>[,model=<model>][,key=<aes key>]\n" \
>> +    "     [,initstate=[bin:|base64:]fd:<fd>|<path>]\n" \
>>       "                enable a builtin TPM with state in file in path\n" \
>>       "                and encrypt the TPM's state with the given AES key\n" \
>> +    "                initstate= path to initial state of TPM; default is base64\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" \
>> @@ -1800,7 +1802,7 @@ Use ? to print all available TPM backend
>>   qemu -tpmdev ?
>>   @end example
>>
>> -@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}]
>> +@item -tpmdev builtin ,id=@var{id}, path=@var{path} [,key=@var{key}] [,initstate=@var{path}]
>>
>>   Creates an instance of the built-in TPM.
>>
>> @@ -1830,6 +1832,40 @@ using AES-CBC encryption scheme supply t
>>   -tpmdev builtin,id=tpm0,path=<path_to_qcow2>,key=aes-cbc:0x1234567890abcdef01234567890abcdef -device tpm-tis,tpmdev=tpm0
>>   @end example
>>
>> +@option{initstate} specifies the path to a file containing the initial
>> +state of the TPM. It can be used to provide the TPM with an EK and certificates
>> +for the EK, TPM and Platform. Since the file contains binary data that
>> +have to conform to the TPM's layout of data, it must have been created using
>> +an approriate authoring tool.
>> +
>> +The initstate option allows to provide a binary state blob or one that is
>> +encode in base 64. The base64-encode state blob must have the format
>> +
>> +@example
>> +-----BEGIN INITSTATE-----
>> +<base 64 encoded state>
>> +-----END INITSTATE-----
>> +@end example
>> +
>> +The initstate option is only effective when Qemu is started with blank
>> +state.
>> +
>> +The initstate option supports several formats:
>> +
>> +@table @option
>> + @item  [base64:]<path_to_blob>
>> + Provide the path to the TPM's initial state blob in base64 format.
>> + @item  bin:<path to blob>
>> + Provide the path to the TPM's initial state blob in binary format.
>> + @item  [base64:]fd:<fd>
>> + Provide the base64 formatted initial state via a file descriptor to read from.
>> + @item  bin:fd:<fd>
>> + Provide the binary initial state via a file descriptor to read from.
> The command line is non standard.  E.g. what if the path starts with fd?
>
Yes, that's a problem. Above would require bin:file:<path to blob> to be 
understood.

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration
  2011-09-01 18:12 ` [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Michael S. Tsirkin
@ 2011-09-02  3:02   ` Stefan Berger
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-02  3:02 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/01/2011 02:12 PM, Michael S. Tsirkin wrote:
> On Wed, Aug 31, 2011 at 10:35:51AM -0400, Stefan Berger wrote:
>> Once TPM support is check in to the Qemu git repository, I would like to
>> force the usage of libtpms-0.5.2.
> So configure will need to test that and disable the backend.
>
Right. I am currently only building libtpms-0.5.1 myself. So for now I 
don't want to add this patch that would prevent everyone from building.

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend
  2011-09-02  1:24     ` Stefan Berger
@ 2011-09-04 16:27       ` Michael S. Tsirkin
  0 siblings, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:27 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 09:24:26PM -0400, Stefan Berger wrote:
> In patch 6 I am adding a skeleton backend driver that I am
> transforming into the libtpms-based backend in patch 7. I didn't
> name the file tpm_skeleton.c but already tpm_builtin.c and all
> functions already start with the prefix tpm_builtin. This presumably
> makes it easier to review since the 'meat' is added in part 7 and
> unnecessary function name changes are avoided.

It is a good idea to split your code to patches.
But you don't need your code to actually work
in an intermediate step - or even compile
if it is not added to the Makefile.

That will help reviewers by sending small patches
and not waste reviewer's time on reading code
removed in a later patch.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-09-02  1:01     ` Stefan Berger
@ 2011-09-04 16:29       ` Michael S. Tsirkin
  2011-09-04 16:50       ` Michael S. Tsirkin
  1 sibling, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:29 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 09:01:32PM -0400, Stefan Berger wrote:
> On 09/01/2011 01:14 PM, Michael S. Tsirkin wrote:
> >On Wed, Aug 31, 2011 at 10:35:52AM -0400, Stefan Berger wrote:
> >>This patch adds support for TPM command line options.
> >>The command line supported here (considering the libtpms based
> >>backend) are
> >>
> >>./qemu-... -tpm builtin,path=<path to blockstorage file>
> >>
> >>and
> >>
> >>./qemu-... -tpmdev builtin,path=<path to blockstorage file>,id=<id>
> >>            -device tpm-tis,tpmdev=<id>
> >do we really need both?
> I had chatted with Anthony about this. I am following the existing
> pattern is use for example for -netdev / -net.

Must be some kind of misunderstanding.
Please do not add multiple ways to do one thing.
We only support -net for legacy reasons.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-02  3:00     ` Stefan Berger
@ 2011-09-04 16:38       ` Michael S. Tsirkin
  2011-09-07  2:45         ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:38 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >Do we really have to hand-craft file reading?
> >How large is TPMPROP_TPM_MAX_NV_SPACE?
> >If not too large, we can just allocate that
> >and do a single fread call?
> Yes, I could do that, too.
> >Or, we rely on glib now - can we use
> >g_io_channel_read_to_end () or something like that?
> >
> >
> GIOStatus   g_io_channel_read_to_end        (GIOChannel *channel,
>                                              gchar **str_return,
>                                              gsize *length,
>                                              GError **error);
> 
> I'd rather not use it. Presumably it uses g_malloc() to internally
> allocate the str_return. However, the buffer we are allocating in
> this function is given to the libtpms, which in turn will call plain
> free() on it. I don't want this to cause a problem if not g_free()
> is called on this buffer.

Well, copy the data and malloc, or do something else,
must be easier than this manual loop.

> >>+
> >Separate options for fd and for file mode would be better.
> >
> initstate_fd=base64:<file descriptor>
> initstate_fd=bin:<file descriptor>
> initstate=base64:<path to file>
> initstate=bin:<path to file>
> 
> Along these lines?

No

initstate_fd=<file descriptor>
initstate_base64=on/off (or base64/bin if you really expect
	more formats in the future)

and use qemu routines to get the fd so they can be
passed through the monitor later ...


> Yes, that's a problem. Above would require bin:file:<path to blob>
> to be understood.
> 
>    Stefan

So avoid it. Give each option it's own flag.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation
  2011-09-02  2:41     ` Stefan Berger
@ 2011-09-04 16:42       ` Michael S. Tsirkin
  0 siblings, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:42 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 10:41:04PM -0400, Stefan Berger wrote:
> On 09/01/2011 01:40 PM, Michael S. Tsirkin wrote:
> >On Wed, Aug 31, 2011 at 10:36:04AM -0400, Stefan Berger wrote:
> >>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.
> >Why limit this to intel platforms then?
> >
> Same as with my previous comment: only hw/pc.c handles the TPM
> device for x86_64 and i386 emulators.
> >>v8:
> >>  - initializing 'in' variable
> >>
> >>Signed-off-by: Stefan Berger<stefanb@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
> >Is this the same one an earlier patch removed?
> It's similar, yes, but previously it served to cut the 'builtin'
> backend's code size into two smaller patches.
> >>+ *
> >>+ *  Copyright (c) 2010, 2011 IBM Corporation
> >>+ *  Copyright (c) 2010, 2011 Stefan Berger
> >Why dual copyright btw?
> >
> Not a specific reason...
> >>+ *
> >>+ * 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 */
> >don't use C++ comments please.
> I ran the perl script over the patches and it pointed that out to
> me. I only kept it because I found similar comments for DEBUG
> #defines in other files.
> >>+
> >>+
> >>+/* 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;
> >this lacks consistency in naming.
> >prefix everything with tpm_null?
> >
> Ok.
> 
> >>+
> >>+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];
> >Use symbolic constants above?
> >
> In this case dev_description could be a constant.
> 
> static char tpm_null_dev_description[] = "Null TPM backend driver";

Right. Same for the tpm_std_fatal_error_response magic numbers ...

> >>+
> >>+
> >>+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
> >Hmm, so now we remove some code added earlier?
> Yes. I added this for the purpose of bisectability but also
> logically previously the --enable-tpm configure parameter caused
> failure in case libtpms was not found to be installed. Now it
> doesn't fail anymore since at least the null driver doesn't have any
> dependencies and  can always be built.
> >Pls don't do this for new code: this makes review harder, not easier.
> >It makes sense for old code to ensure bisectability.
> So how should I change it? Should I not add this failure notice in
> the first place? How should the build configure script then handle
> the --enable-tpm if it cannot find libtpms and thus cannot fulfill
> the --enable-tpm?
> 
>    Stefan

Simply stuff to configure and makefile in the last patch.
The rest of the code can then be put in the final form directly
without bisectability issues as it does not compile until
fully applied.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options
  2011-09-02  1:01     ` Stefan Berger
  2011-09-04 16:29       ` Michael S. Tsirkin
@ 2011-09-04 16:50       ` Michael S. Tsirkin
  1 sibling, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:50 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 09:01:32PM -0400, Stefan Berger wrote:
> >>Monitor support for 'info tpm' has been added. It for example prints the
> >>following:
> >>
> >>TPM devices:
> >>   builtin: model=tpm-tis,id=tpm0
> >This mixes frontend and backend properties.
> >
> There's currently only one frontend 'model' and that's the
> 'tpm-tis'. In case someone would want to write a virtio equivalent
> it would show the that the 'builtin' backend is connected to the
> 'virtio' frontend model. If above is not correct, how should it look
> like?

E.g. for net: we list backends and frontends each with its
properties.

  virtio-net-pci.0: type=nic,model=virtio-net-pci,macaddr=52:54:00:12:34:56
   \ foo: type=tap,ifname=msttap0,script=/home/mst/ifup,downscript=no



> >>+
> >>+    value = qemu_opt_get(opts, "type");
> >>+    if (!value) {
> >>+        qerror_report(QERR_MISSING_PARAMETER, "type");
> >>+        tpm_display_backend_drivers(stderr);
> >>+        return 1;
> >>+    }
> >>+
> >>+    be = tpm_get_backend_driver(value);
> >>+    if (be == NULL) {
> >>+        qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
> >>+                      "a tpm backend type");
> >>+        tpm_display_backend_drivers(stderr);
> >>+        return 1;
> >>+    }
> >>+
> >>+    assert((is_tpmdev&&  model == NULL) || (!is_tpmdev&&  model != NULL));
> >Why isn't this using qdev for parameter passing?
> >
> Can you point me to a device that is using qdev for parameter
> passing.


virtio-pci devices: block, net - have a huge number of these.

I'm talking about frontend primarily.

> Also this part is very similar to how the networking works
> (net.c).
> 
>    Stefan

A large part of that is legacy processing.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-02  2:23     ` Stefan Berger
@ 2011-09-04 16:58       ` Michael S. Tsirkin
  2011-09-07  0:32         ` Stefan Berger
  2011-09-07 18:55       ` Michael S. Tsirkin
  1 sibling, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 16:58 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
> >>Checks are added that test
> >>- whether encryption is supported follwing the revision of the directory
> >>   structure (rev>= 2)
> >You never generate rev 1 code, right?
> I did this in the previous patch that implemented rev 1 that knew
> nothing about the encryption added in rev 2.
> >So why keep that support around in code?
> >The first version merged into qemu should be revision 0 (or 1, as you like).
> I chose '1'. See patch 9:
> 
> +#define BS_DIR_REV1         1
> +
> +#define BS_DIR_REV_CURRENT  BS_DIR_REV1
> +
> 
> So I think it's the proper thing to do to increase the revision
> number from 1 to 2 since it's in two separate patches (even if they
> were to be applied immediately).

Look, the code is not merged yet and you already have
legacy with revision history to support? Why create work?

> >Don't support legacy with old version of your patch.
> >
> >>- whether a key has been provided although all data are stored in clear-text
> >>- whether a key is missing for decryption.
> >>
> >>In either one of the cases the backend reports an error message to the user
> >>and Qemu terminates.
> >>
> >>-v7:
> >>   - cleaned up function parsing key
> >>
> >>-v6:
> >>   - changed the format of the key= to take the type of encryption into
> >>     account: key=aes-cbc:0x12345... and reworked code for encryption and
> >>     decryption of blobs;
> >separate type and data:
> >keytype=aes-cbc,key=0x123  to avoid introducing more option parsing.
> >Also, are people likely to have the key in a file?
> >If yes maybe read a key from there and skip parsing completely?
> >
> I think both choices should probably exist. Now what's a good file
> format? Would we expect to find a hex number in there or should it
> always be assumed to be a binary file?

binary

> >>   - modified directory entry to hold a uint_8 describing the encryption
> >>     type (none, aes-cbc) being used for the blobs.
> >>   - incrementing revision of the directory to '2' indicating encryption
> >>     support
> >>
> >>-v5:
> >>   - -tpmdev now also gets a key parameter
> >>   - add documentation about key parameter
> >>
> >>Signed-off-by: Stefan Berger<stefanb@linux.vnet.ibm.com>
> >>
> >>---
> >>  hw/tpm_builtin.c |  285 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
> >>  qemu-config.c    |   10 +
> >>  qemu-options.hx  |   22 +++-
> >>  tpm.c            |   10 +
> >>  4 files changed, 318 insertions(+), 9 deletions(-)
> >>
> >>Index: qemu-git/hw/tpm_builtin.c
> >>===================================================================
> >>--- qemu-git.orig/hw/tpm_builtin.c
> >>+++ qemu-git/hw/tpm_builtin.c
> >>@@ -27,6 +27,7 @@
> >>  #include "hw/pc.h"
> >>  #include "migration.h"
> >>  #include "sysemu.h"
> >>+#include "aes.h"
> >>
> >>  #include<libtpms/tpm_library.h>
> >>  #include<libtpms/tpm_error.h>
> >>@@ -110,14 +111,27 @@ typedef struct BSDir {
> >>      uint16_t  rev;
> >>      uint32_t  checksum;
> >>      uint32_t  num_entries;
> >>-    uint32_t  reserved[10];
> >>+    uint8_t   enctype;
> >>+    uint8_t   reserved1[3];
> >>+    uint32_t  reserved[8];
> >>      BSEntry   entries[BS_DIR_MAX_NUM_ENTRIES];
> >>  } __attribute__((packed)) BSDir;
> >>
> >>
> >>  #define BS_DIR_REV1         1
> >>+/* rev 2 added encryption */
> >>+#define BS_DIR_REV2         2
> >>
> >>-#define BS_DIR_REV_CURRENT  BS_DIR_REV1
> >>+
> >>+#define BS_DIR_REV_CURRENT  BS_DIR_REV2
> >>+
> >>+/* above enctype */
> >>+enum BSEnctype {
> >>+    BS_DIR_ENCTYPE_NONE = 0,
> >>+    BS_DIR_ENCTYPE_AES_CBC,
> >>+
> >>+    BS_DIR_ENCTYPE_LAST,
> >>+};
> >>
> >>  /* local variables */
> >>
> >>@@ -150,6 +164,11 @@ static const unsigned char tpm_std_fatal
> >>
> >>  static char dev_description[80];
> >>
> >>+static struct enckey {
> >>+    uint8_t enctype;
> >>+    AES_KEY tpm_enc_key;
> >>+    AES_KEY tpm_dec_key;
> >>+} enckey;
> >>
> >>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
> >>                                                 enum BSEntryType be,
> >>@@ -264,7 +283,7 @@ static uint32_t tpm_builtin_calc_dir_che
> >>
> >>  static bool tpm_builtin_is_valid_bsdir(BSDir *dir)
> >>  {
> >>-    if (dir->rev != BS_DIR_REV_CURRENT ||
> >>+    if (dir->rev>  BS_DIR_REV_CURRENT ||
> >>          dir->num_entries>  BS_DIR_MAX_NUM_ENTRIES) {
> >>          return false;
> >>      }
> >>@@ -295,6 +314,33 @@ static bool tpm_builtin_has_valid_conten
> >>      return rc;
> >>  }
> >>
> >>+static bool tpm_builtin_supports_encryption(const BSDir *dir)
> >>+{
> >>+    return (dir->rev>= BS_DIR_REV2);
> >>+}
> >>+
> >>+
> >>+static bool tpm_builtin_has_missing_key(const BSDir *dir)
> >>+{
> >>+    return ((dir->enctype   != BS_DIR_ENCTYPE_NONE)&&
> >>+            (enckey.enctype == BS_DIR_ENCTYPE_NONE));
> >>+}
> >>+
> >>+
> >>+static bool tpm_builtin_has_unnecessary_key(const BSDir *dir)
> >>+{
> >>+    return (((dir->enctype   == BS_DIR_ENCTYPE_NONE)&&
> >>+             (enckey.enctype != BS_DIR_ENCTYPE_NONE)) ||
> >>+            ((!tpm_builtin_supports_encryption(dir))&&
> >>+             (enckey.enctype != BS_DIR_ENCTYPE_NONE)));
> >>+}
> >>+
> >>+
> >>+static bool tpm_builtin_uses_unsupported_enctype(const BSDir *dir)
> >>+{
> >>+    return (dir->enctype>= BS_DIR_ENCTYPE_LAST);
> >>+}
> >>+
> >>
> >>  static int tpm_builtin_create_blank_dir(BlockDriverState *bs)
> >>  {
> >>@@ -306,6 +352,7 @@ static int tpm_builtin_create_blank_dir(
> >>      dir = (BSDir *)buf;
> >>      dir->rev = BS_DIR_REV_CURRENT;
> >>      dir->num_entries = 0;
> >>+    dir->enctype = enckey.enctype;
> >>
> >>      dir->checksum = tpm_builtin_calc_dir_checksum(dir);
> >>
> >>@@ -407,6 +454,38 @@ static int tpm_builtin_startup_bs(BlockD
> >>
> >>      tpm_builtin_dir_be_to_cpu(dir);
> >>
> >>+    if (tpm_builtin_is_valid_bsdir(dir)) {
> >>+        if (tpm_builtin_supports_encryption(dir)&&
> >>+            tpm_builtin_has_missing_key(dir)) {
> >>+            fprintf(stderr,
> >>+                    "tpm: the data are encrypted but I am missing the key.\n");
> >>+            rc = -EIO;
> >>+            goto err_exit;
> >>+        }
> >>+        if (tpm_builtin_has_unnecessary_key(dir)) {
> >>+            fprintf(stderr,
> >>+                    "tpm: I have a key but the data are not encrypted.\n");
> >>+            rc = -EIO;
> >>+            goto err_exit;
> >>+        }
> >>+        if (tpm_builtin_supports_encryption(dir)&&
> >>+            tpm_builtin_uses_unsupported_enctype(dir)) {
> >>+            fprintf(stderr,
> >>+                    "tpm: State is encrypted with an unsupported encryption "
> >>+                    "scheme.\n");
> >>+            rc = -EIO;
> >>+            goto err_exit;
> >>+        }
> >>+        if (tpm_builtin_supports_encryption(dir)&&
> >>+            (dir->enctype != BS_DIR_ENCTYPE_NONE)&&
> >>+            !tpm_builtin_has_valid_content(dir)) {
> >>+            fprintf(stderr, "tpm: cannot read the data - "
> >>+                    "is this the wrong key?\n");
> >>+            rc = -EIO;
> >>+            goto err_exit;
> >>+        }
> >>+    }
> >>+
> >>      if (!tpm_builtin_is_valid_bsdir(dir) ||
> >>          !tpm_builtin_has_valid_content(dir)) {
> >>          /* if it's encrypted and has something else than null-content,
> >>@@ -569,6 +648,105 @@ static int set_bs_entry_size_crc(BlockDr
> >>  }
> >>
> >>
> >>+static int tpm_builtin_blocksize_roundup(uint8_t enctype, int plainsize)
> >>+{
> >>+    switch (enctype) {
> >>+    case BS_DIR_ENCTYPE_NONE:
> >>+        return plainsize;
> >>+    case BS_DIR_ENCTYPE_AES_CBC:
> >>+        return ALIGN(plainsize, AES_BLOCK_SIZE);
> >>+    default:
> >>+        assert(false);
> >>+        return 0;
> >>+    }
> >>+}
> >>+
> >>+
> >>+static int tpm_builtin_bdrv_pread(BlockDriverState *bs, int64_t offset,
> >>+                                  void *buf, int count,
> >>+                                  enum BSEntryType type)
> >>+{
> >>+    int ret;
> >>+    union {
> >>+        uint64_t ll[2];
> >>+        uint8_t b[16];
> >>+    } ivec;
> >>+    int toread = count;
> >>+
> >>+    toread = tpm_builtin_blocksize_roundup(enckey.enctype, count);
> >>+
> >>+    ret = bdrv_pread(bs, offset, buf, toread);
> >>+
> >>+    if (ret != toread) {
> >>+        return ret;
> >>+    }
> >>+
> >>+    switch (enckey.enctype) {
> >>+    case BS_DIR_ENCTYPE_NONE:
> >>+        break;
> >>+    case BS_DIR_ENCTYPE_AES_CBC:
> >>+        ivec.ll[0] = cpu_to_be64(type);
> >>+        ivec.ll[1] = 0;
> >>+
> >>+        AES_cbc_encrypt(buf, buf, toread,&enckey.tpm_dec_key, ivec.b, 0);
> >>+        break;
> >>+    default:
> >>+        assert(false);
> >>+    }
> >>+
> >>+    return count;
> >>+}
> >>+
> >>+
> >>+static int tpm_builtin_bdrv_pwrite(BlockDriverState *bs, int64_t offset,
> >>+                                   void *buf, int count,
> >>+                                   enum BSEntryType type)
> >>+{
> >>+    int ret;
> >>+    union {
> >>+        uint64_t ll[2];
> >>+        uint8_t b[16];
> >>+    } ivec;
> >>+    int towrite = count;
> >>+    void *out_buf = buf;
> >>+
> >>+    switch (enckey.enctype) {
> >>+    case BS_DIR_ENCTYPE_NONE:
> >>+        break;
> >>+    case BS_DIR_ENCTYPE_AES_CBC:
> >>+        ivec.ll[0] = cpu_to_be64(type);
> >>+        ivec.ll[1] = 0;
> >>+
> >>+        towrite = ALIGN(count, AES_BLOCK_SIZE);
> >>+
> >>+        if (towrite != count) {
> >>+            out_buf = g_malloc(towrite);
> >>+
> >>+            if (out_buf == NULL) {
> >>+                return -ENOMEM;
> >>+            }
> >>+        }
> >>+
> >>+        AES_cbc_encrypt(buf, out_buf, towrite,&enckey.tpm_enc_key, ivec.b, 1);
> >>+        break;
> >>+    default:
> >>+        assert(false);
> >>+    }
> >>+
> >>+    ret = bdrv_pwrite(bs, offset, out_buf, towrite);
> >>+
> >>+    if (out_buf != buf) {
> >>+        g_free(out_buf);
> >>+    }
> >>+
> >>+    if (ret == towrite) {
> >>+        return count;
> >>+    }
> >>+
> >>+    return ret;
> >>+}
> >>+
> >>+
> >>  static int tpm_builtin_load_sized_data_from_bs(BlockDriverState *bs,
> >>                                                 enum BSEntryType be,
> >>                                                 TPMSizedBuffer *tsb)
> >>@@ -594,7 +772,7 @@ static int tpm_builtin_load_sized_data_f
> >>          goto err_exit;
> >>      }
> >>
> >>-    tsb->buffer = g_malloc(entry.blobsize);
> >>+    tsb->buffer = g_malloc(ALIGN(entry.blobsize, AES_BLOCK_SIZE));
> >>      if (!tsb->buffer) {
> >>          rc = -ENOMEM;
> >>          goto err_exit;
> >>@@ -602,7 +780,8 @@ static int tpm_builtin_load_sized_data_f
> >>
> >>      tsb->size = entry.blobsize;
> >>
> >>-    if (bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size) != tsb->size) {
> >>+    if (tpm_builtin_bdrv_pread(bs, entry.offset, tsb->buffer, tsb->size, be) !=
> >>+        tsb->size) {
> >>          clear_sized_buffer(tsb);
> >>          fprintf(stderr, "tpm: Error while reading stored data!\n");
> >>          rc = -EIO;
> >>@@ -667,7 +846,8 @@ static int tpm_builtin_save_sized_data_t
> >>      }
> >>
> >>      if (data_len>  0) {
> >>-        if (bdrv_pwrite(bs, entry.offset, data, data_len) != data_len) {
> >>+        if (tpm_builtin_bdrv_pwrite(bs, entry.offset, data, data_len, be) !=
> >>+            data_len) {
> >>              rc = -EIO;
> >>          }
> >>      }
> >>@@ -1492,11 +1672,77 @@ static const char *tpm_builtin_create_de
> >>  }
> >>
> >>
> >>+/*
> >>+ * Convert a string of hex digits to its binary representation.
> >>+ * The conversion stops once either the maximum size of the binary
> >>+ * array has been reached or an non-hex digit was encountered.
> >>+ */
> >Don't we care about non-hex following a valid key?
> Do you have an example? This function is meant to convert 0x1234567
> to a binary stream. An equivalent would be 0x1234567X, since it
> would terminate parsing on 'X'.

But than might be a typo.
0x1234567O
for example stops parsing at 'O' since this is not a digit but
looks like one.


> >Two empty lines in a row :)
> >
> Probably this is not the only occurrence... Is this a problem?

Yes. They aren't helpful. checkpatch should complain about these
not sure whether it does.

> >>+static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
> >>+                                        unsigned char *keyvalue,
> >>+                                        int *keysize)
> >>+{
> >>+    size_t c = 0;
> >>+
> >>+    /* skip over leading '0x' */
> >>+    if (!strncmp(rawkey, "0x", 2)) {
> >>+        rawkey += 2;
> >>+    }
> >>+
> >>+    c = stream_to_bin(rawkey, keyvalue, *keysize);
> >>+
> >>+    if (c == 256/4) {
> >>+        *keysize = 256;
> >>+    } else if (c>= 192/4) {
> >>+        *keysize = 192;
> >>+    } else if (c>= 128/4) {
> >>+        *keysize = 128;
> >>+    } else {
> >>+        return false;
> >Want to tell the user what went wrong?
> Here's what the key parser handles:
> - all keys >= 256 bits are truncated to 256 bits
> - all keys >= 192 bits are truncated to 192 bits
> - all keys >= 128 bits are truncated to 128 bits
> - all keys < 128 bits are assumed to not be given as a hexadecimal
> number but the string itself is the key, i.e. 'HELLOWORLD' becomes a
> valid key.

Oh my. I presume this makes sense in some world ...

> >Also, you don't allow skipping leading zeroes?
> An AES key should be allowed to have leading zeros, no?
> >>+    }
> >>+
> >>+    return true;
> >Always put spaces around /.
> >But where does the /4 come from? 4 bits per character?
> >
> c is the number of 'nibbles'. 4 bits in a nibble - that's what the
> /4 comes from.
> >>+}
> >>+
> >>+
> >>  static TPMBackend *tpm_builtin_create(QemuOpts *opts, const char *id,
> >>                                        const char *model)
> >>  {
> >>      TPMBackend *driver;
> >>      const char *value;
> >>+    unsigned char keyvalue[256/8];
> >>+    int keysize = sizeof(keyvalue);
> >>
> >>      driver = g_malloc(sizeof(TPMBackend));
> >>      if (!driver) {
> >>@@ -1523,6 +1769,33 @@ static TPMBackend *tpm_builtin_create(Qe
> >>          goto err_exit;
> >>      }
> >>
> >>+    value = qemu_opt_get(opts, "key");
> >>+    if (value) {
> >>+        if (!strncasecmp(value, "aes-cbc:", 8)) {
> >>+            memset(keyvalue, 0x0, sizeof(keyvalue));
> >>+
> >>+            if (!tpm_builtin_parse_as_hexkey(&value[8], keyvalue,&keysize)) {
> >>+                keysize = 128;
> >>+                strncpy((char *)keyvalue, value, 128/8);
> Here first the input is attempted to be parsed as hex key and if
> that fails the input string is taken as the key. It should be
> &value[8] here -- so that's a bug.
> 
>   Stefan

These should be different options.

key_format=hex/string

etc.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-02  1:53     ` Stefan Berger
@ 2011-09-04 19:32       ` Michael S. Tsirkin
  2011-09-06 23:55         ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-04 19:32 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 09:53:40PM -0400, Stefan Berger wrote:
> >Generally, what all other devices do is perform validation
> >as the last step in migration when device state
> >is restored. On failure, management can decide what to do:
> >retry migration or restart on source.
> >
> >Why is TPM special and needs to be treated differently?
> >
> >
> >

...

> 
> More detail: Typically one starts out with an empty QCoW2 file
> created via qemu-img. Once Qemu starts and initializes the
> libtpms-based TPM, it tries to read existing state from that QCoW2
> file. Since there is no state stored in the QCoW2, the TPM will
> start initializing itself to an initial 'blank' state.

So it looks like the problem is you access the file when guest isn't
running.  Delaying the initialization until the guest actually starts
running will solve the problem in a way that is more consistent with
other qemu devices.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-04 19:32       ` Michael S. Tsirkin
@ 2011-09-06 23:55         ` Stefan Berger
  2011-09-07 11:18           ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-06 23:55 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/04/2011 03:32 PM, Michael S. Tsirkin wrote:
> On Thu, Sep 01, 2011 at 09:53:40PM -0400, Stefan Berger wrote:
>>> Generally, what all other devices do is perform validation
>>> as the last step in migration when device state
>>> is restored. On failure, management can decide what to do:
>>> retry migration or restart on source.
>>>
>>> Why is TPM special and needs to be treated differently?
>>>
>>>
>>>
> ...
>
>> More detail: Typically one starts out with an empty QCoW2 file
>> created via qemu-img. Once Qemu starts and initializes the
>> libtpms-based TPM, it tries to read existing state from that QCoW2
>> file. Since there is no state stored in the QCoW2, the TPM will
>> start initializing itself to an initial 'blank' state.
> So it looks like the problem is you access the file when guest isn't
> running.  Delaying the initialization until the guest actually starts
> running will solve the problem in a way that is more consistent with
> other qemu devices.
>
I'd agree if there wasn't one more thing: encryption on the data inside 
the QCoW2 filesystem

First: There are two ways to encrypt the data.

One comes with the QCoW2 type of image and it comes for free. Set the 
encryption flag when creating the QCoW2 file and one has to provide a 
key to access the QCoW2. I found this mode problematic for users since 
it required me to go through the monitor every time I started the VM. 
Besides that the key is provided so late that all devices are already 
initialized and if the wrong key was provided the only thing the TPM can 
do is to go into shutdown mode since there is state on the QCoW2 but it 
cannot be decrypted. This also became problematic when doing migrations 
with libvirt for example and one was to have a wrong key/password 
installed on the target side -- graceful termination of the migration is 
impossible.

So the above drove the implementation of the encryption mode added in 
patch 10 in this series. Here the key is provided via command line and 
it can be used immediately. So I am reading the state blobs from the 
file, decrypt them, create the CRC32 on the plain data and check against 
the CRC32 stored in the 'directory'. If it doesn't match the expected 
CRC32 either the key was wrong or the state is corrupted and I can 
terminate Qemu gracefully. I can also react appropriately if no key was 
provided but one is expected and vice-versa. Also in case of migration 
this now allows me to terminate Qemu gracefully so it continues running 
on the source. This is an improvement over the situation described above 
where in case the target had the wrong key the TPM went into shutdown 
mode and the user would be wondering why that is -- the TPM becomes 
inaccessible. However, particularly in the case of migration with shared 
storage I need to access the QCoW2 file to check whether on the target I 
have the right key. And this happens very early during qemu startup on 
the target machine. So that's where the file locking on the block layer 
comes in. I want to serialize access to the QCoW2 so I don't read 
intermediate state on the migration-target host that can occur if the 
source is currently writing TPM state data.

This patch and the command line provided key along with the encryption 
mode added in patch 10 in this series add to usability and help prevent 
failures.

Regards,
      Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-04 16:58       ` Michael S. Tsirkin
@ 2011-09-07  0:32         ` Stefan Berger
  2011-09-07 11:59           ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07  0:32 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/04/2011 12:58 PM, Michael S. Tsirkin wrote:
> On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
>>>> Checks are added that test
>>>> - whether encryption is supported follwing the revision of the directory
>>>>    structure (rev>= 2)
>>> You never generate rev 1 code, right?
>> I did this in the previous patch that implemented rev 1 that knew
>> nothing about the encryption added in rev 2.
>>> So why keep that support around in code?
>>> The first version merged into qemu should be revision 0 (or 1, as you like).
>> I chose '1'. See patch 9:
>>
>> +#define BS_DIR_REV1         1
>> +
>> +#define BS_DIR_REV_CURRENT  BS_DIR_REV1
>> +
>>
>> So I think it's the proper thing to do to increase the revision
>> number from 1 to 2 since it's in two separate patches (even if they
>> were to be applied immediately).
> Look, the code is not merged yet and you already have
> legacy with revision history to support? Why create work?
>
Ok, I won't build the intermediate version then.
>>> Don't support legacy with old version of your patch.
>>>
>>>> - whether a key has been provided although all data are stored in clear-text
>>>> - whether a key is missing for decryption.
>>>>
>>>> In either one of the cases the backend reports an error message to the user
>>>> and Qemu terminates.
>>>>
>>>> -v7:
>>>>    - cleaned up function parsing key
>>>>
>>>> -v6:
>>>>    - changed the format of the key= to take the type of encryption into
>>>>      account: key=aes-cbc:0x12345... and reworked code for encryption and
>>>>      decryption of blobs;
>>> separate type and data:
>>> keytype=aes-cbc,key=0x123  to avoid introducing more option parsing.
>>> Also, are people likely to have the key in a file?
>>> If yes maybe read a key from there and skip parsing completely?
>>>
>> I think both choices should probably exist. Now what's a good file
>> format? Would we expect to find a hex number in there or should it
>> always be assumed to be a binary file?
> binary
So then I'd keep the possibility to pass the key via command line and 
add the option to read it from a file as well.
>>> Two empty lines in a row :)
>>>
>> Probably this is not the only occurrence... Is this a problem?
> Yes. They aren't helpful. checkpatch should complain about these
> not sure whether it does.
It so far doesn't.
>>>> +static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
>>>> +                                        unsigned char *keyvalue,
>>>> +                                        int *keysize)
>>>> +{
>>>> +    size_t c = 0;
>>>> +
>>>> +    /* skip over leading '0x' */
>>>> +    if (!strncmp(rawkey, "0x", 2)) {
>>>> +        rawkey += 2;
>>>> +    }
>>>> +
>>>> +    c = stream_to_bin(rawkey, keyvalue, *keysize);
>>>> +
>>>> +    if (c == 256/4) {
>>>> +        *keysize = 256;
>>>> +    } else if (c>= 192/4) {
>>>> +        *keysize = 192;
>>>> +    } else if (c>= 128/4) {
>>>> +        *keysize = 128;
>>>> +    } else {
>>>> +        return false;
>>> Want to tell the user what went wrong?
>> Here's what the key parser handles:
>> - all keys>= 256 bits are truncated to 256 bits
>> - all keys>= 192 bits are truncated to 192 bits
>> - all keys>= 128 bits are truncated to 128 bits
>> - all keys<  128 bits are assumed to not be given as a hexadecimal
>> number but the string itself is the key, i.e. 'HELLOWORLD' becomes a
>> valid key.
> Oh my. I presume this makes sense in some world ...
>
So then I will require to have the exact size of a 128, 192 or 256 bit 
key then and maybe allow 'passwords', which really was what the last 
example above was falling back to. Looking around in the QCoW2 code 
passwords seem to be also possible there (block/qcow2.c:qcow_set_key()).

>>> Also, you don't allow skipping leading zeroes?
>> An AES key should be allowed to have leading zeros, no?
>>>> +    }
>>>> +
>>>> +    return true;
>>> Always put spaces around /.
>>> But where does the /4 come from? 4 bits per character?
>>>
>> c is the number of 'nibbles'. 4 bits in a nibble - that's what the
>> /4 comes from.
>>>> +}
>>>> +
>>>> +
>>>>   static TPMBackend *tpm_builtin_create(QemuOpts *opts, const char *id,
>>>>                                         const char *model)
>>>>   {
>>>>       TPMBackend *driver;
>>>>       const char *value;
>>>> +    unsigned char keyvalue[256/8];
>>>> +    int keysize = sizeof(keyvalue);
>>>>
>>>>       driver = g_malloc(sizeof(TPMBackend));
>>>>       if (!driver) {
>>>> @@ -1523,6 +1769,33 @@ static TPMBackend *tpm_builtin_create(Qe
>>>>           goto err_exit;
>>>>       }
>>>>
>>>> +    value = qemu_opt_get(opts, "key");
>>>> +    if (value) {
>>>> +        if (!strncasecmp(value, "aes-cbc:", 8)) {
>>>> +            memset(keyvalue, 0x0, sizeof(keyvalue));
>>>> +
>>>> +            if (!tpm_builtin_parse_as_hexkey(&value[8], keyvalue,&keysize)) {
>>>> +                keysize = 128;
>>>> +                strncpy((char *)keyvalue, value, 128/8);
>> Here first the input is attempted to be parsed as hex key and if
>> that fails the input string is taken as the key. It should be
>> &value[8] here -- so that's a bug.
>>
>>    Stefan
> These should be different options.
>
> key_format=hex/string
>
> etc.
>
To summarize it:
enc_mode=<aes-cbc>        # redundant for now since this is the only 
supported encryption scheme; so could drop it and assume as default

key_format=<hex/binary> # hex for a string hex number; binary would mean 
the found string is directly converted to a char[] that is then directly 
used as the AES key (like a password)

key=<128, 192, or 256 bit>hex key or string

key_file=<path to file containing 128,192 or 256 bit hex key or string>

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-04 16:38       ` Michael S. Tsirkin
@ 2011-09-07  2:45         ` Stefan Berger
  2011-09-07 11:23           ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07  2:45 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
>
> initstate_fd=<file descriptor>
> initstate_base64=on/off (or base64/bin if you really expect
> 	more formats in the future)
>
> and use qemu routines to get the fd so they can be
> passed through the monitor later ...
>
I suppose you mean monitor_get_fd(). That functions seems to only be 
used by net.c so far and if  understand the chain of functions correctly 
that are called with the monitor as parameter it helps in hotplugging 
net devices ? For the TPM I would like to *not* have support for 
hotplugging since that device is supposed to be soldered to the 
motherboard and needs to be initialized through a command sequence by 
the (v)BIOS, so it has to be present early on during machine startup.

   Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-06 23:55         ` Stefan Berger
@ 2011-09-07 11:18           ` Michael S. Tsirkin
  2011-09-07 13:06             ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 11:18 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Tue, Sep 06, 2011 at 07:55:48PM -0400, Stefan Berger wrote:
> On 09/04/2011 03:32 PM, Michael S. Tsirkin wrote:
> >On Thu, Sep 01, 2011 at 09:53:40PM -0400, Stefan Berger wrote:
> >>>Generally, what all other devices do is perform validation
> >>>as the last step in migration when device state
> >>>is restored. On failure, management can decide what to do:
> >>>retry migration or restart on source.
> >>>
> >>>Why is TPM special and needs to be treated differently?
> >>>
> >>>
> >>>
> >...
> >
> >>More detail: Typically one starts out with an empty QCoW2 file
> >>created via qemu-img. Once Qemu starts and initializes the
> >>libtpms-based TPM, it tries to read existing state from that QCoW2
> >>file. Since there is no state stored in the QCoW2, the TPM will
> >>start initializing itself to an initial 'blank' state.
> >So it looks like the problem is you access the file when guest isn't
> >running.  Delaying the initialization until the guest actually starts
> >running will solve the problem in a way that is more consistent with
> >other qemu devices.
> >
> I'd agree if there wasn't one more thing: encryption on the data
> inside the QCoW2 filesystem
> 
> First: There are two ways to encrypt the data.
> 
> One comes with the QCoW2 type of image and it comes for free. Set
> the encryption flag when creating the QCoW2 file and one has to
> provide a key to access the QCoW2. I found this mode problematic for
> users since it required me to go through the monitor every time I
> started the VM. Besides that the key is provided so late that all
> devices are already initialized and if the wrong key was provided
> the only thing the TPM can do is to go into shutdown mode since
> there is state on the QCoW2 but it cannot be decrypted. This also
> became problematic when doing migrations with libvirt for example
> and one was to have a wrong key/password installed on the target
> side -- graceful termination of the migration is impossible.
> 
> So the above drove the implementation of the encryption mode added
> in patch 10 in this series. Here the key is provided via command
> line and it can be used immediately. So I am reading the state blobs
> from the file, decrypt them, create the CRC32 on the plain data and
> check against the CRC32 stored in the 'directory'. If it doesn't
> match the expected CRC32 either the key was wrong or the state is
> corrupted and I can terminate Qemu gracefully. I can also react
> appropriately if no key was provided but one is expected and
> vice-versa. Also in case of migration this now allows me to
> terminate Qemu gracefully so it continues running on the source.
> This is an improvement over the situation described above where in
> case the target had the wrong key the TPM went into shutdown mode
> and the user would be wondering why that is -- the TPM becomes
> inaccessible. However, particularly in the case of migration with
> shared storage I need to access the QCoW2 file to check whether on
> the target I have the right key. And this happens very early during
> qemu startup on the target machine. So that's where the file locking
> on the block layer comes in. I want to serialize access to the QCoW2
> so I don't read intermediate state on the migration-target host that
> can occur if the source is currently writing TPM state data.
> 
> This patch and the command line provided key along with the
> encryption mode added in patch 10 in this series add to usability
> and help prevent failures.
> 
> Regards,
>      Stefan

Sure, it's a useful feature of validating the device early.
But as I said above, it's useful for other devices besides
TPM. However it introduces issues where state changes
since guest keeps running (such as what you have described here).

I think solving this in a way specific to TPM is a mistake.
Passing key on command line does not mean you must use it
immediately, this can still be done when guest starts running.

Further, the patchset seems to be split in
a way that introduces support headaches and makes review
harder, not easier. In this case, we will have to support
both a broken mode (key supplied later) and a non-broken one
(key supplied on init). It would be better to implement
a single mode, in a single patch.


-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-07  2:45         ` Stefan Berger
@ 2011-09-07 11:23           ` Michael S. Tsirkin
  2011-09-07 13:51             ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 11:23 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
> On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> >On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >
> >initstate_fd=<file descriptor>
> >initstate_base64=on/off (or base64/bin if you really expect
> >	more formats in the future)
> >
> >and use qemu routines to get the fd so they can be
> >passed through the monitor later ...
> >
> I suppose you mean monitor_get_fd(). That functions seems to only be
> used by net.c so far and if  understand the chain of functions
> correctly that are called with the monitor as parameter it helps in
> hotplugging net devices ? For the TPM I would like to *not* have
> support for hotplugging since that device is supposed to be soldered
> to the motherboard and needs to be initialized through a command
> sequence by the (v)BIOS, so it has to be present early on during
> machine startup.
> 
>   Stefan

Fine, but let's reuse common functions and save code duplication,
especially parsing functions.

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-07  0:32         ` Stefan Berger
@ 2011-09-07 11:59           ` Michael S. Tsirkin
  0 siblings, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 11:59 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Tue, Sep 06, 2011 at 08:32:41PM -0400, Stefan Berger wrote:
> To summarize it:
> enc_mode=<aes-cbc>        # redundant for now since this is the only
> supported encryption scheme; so could drop it and assume as default
> 
> key_format=<hex/binary> # hex for a string hex number; binary would
> mean the found string is directly converted to a char[] that is then
> directly used as the AES key (like a password)
> 
> key=<128, 192, or 256 bit>hex key or string
> 
> key_file=<path to file containing 128,192 or 256 bit hex key or string>
> 
>    Stefan

OK


-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 11:18           ` Michael S. Tsirkin
@ 2011-09-07 13:06             ` Stefan Berger
  2011-09-07 13:16               ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 13:06 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On 09/07/2011 07:18 AM, Michael S. Tsirkin wrote:
> On Tue, Sep 06, 2011 at 07:55:48PM -0400, Stefan Berger wrote:
>> On 09/04/2011 03:32 PM, Michael S. Tsirkin wrote:
>>> On Thu, Sep 01, 2011 at 09:53:40PM -0400, Stefan Berger wrote:
>>>>> Generally, what all other devices do is perform validation
>>>>> as the last step in migration when device state
>>>>> is restored. On failure, management can decide what to do:
>>>>> retry migration or restart on source.
>>>>>
>>>>> Why is TPM special and needs to be treated differently?
>>>>>
>>>>>
>>>>>
>>> ...
>>>
>>>> More detail: Typically one starts out with an empty QCoW2 file
>>>> created via qemu-img. Once Qemu starts and initializes the
>>>> libtpms-based TPM, it tries to read existing state from that QCoW2
>>>> file. Since there is no state stored in the QCoW2, the TPM will
>>>> start initializing itself to an initial 'blank' state.
>>> So it looks like the problem is you access the file when guest isn't
>>> running.  Delaying the initialization until the guest actually starts
>>> running will solve the problem in a way that is more consistent with
>>> other qemu devices.
>>>
>> I'd agree if there wasn't one more thing: encryption on the data
>> inside the QCoW2 filesystem
>>
>> First: There are two ways to encrypt the data.
>>
>> One comes with the QCoW2 type of image and it comes for free. Set
>> the encryption flag when creating the QCoW2 file and one has to
>> provide a key to access the QCoW2. I found this mode problematic for
>> users since it required me to go through the monitor every time I
>> started the VM. Besides that the key is provided so late that all
>> devices are already initialized and if the wrong key was provided
>> the only thing the TPM can do is to go into shutdown mode since
>> there is state on the QCoW2 but it cannot be decrypted. This also
>> became problematic when doing migrations with libvirt for example
>> and one was to have a wrong key/password installed on the target
>> side -- graceful termination of the migration is impossible.
>>
>> So the above drove the implementation of the encryption mode added
>> in patch 10 in this series. Here the key is provided via command
>> line and it can be used immediately. So I am reading the state blobs
>> from the file, decrypt them, create the CRC32 on the plain data and
>> check against the CRC32 stored in the 'directory'. If it doesn't
>> match the expected CRC32 either the key was wrong or the state is
>> corrupted and I can terminate Qemu gracefully. I can also react
>> appropriately if no key was provided but one is expected and
>> vice-versa. Also in case of migration this now allows me to
>> terminate Qemu gracefully so it continues running on the source.
>> This is an improvement over the situation described above where in
>> case the target had the wrong key the TPM went into shutdown mode
>> and the user would be wondering why that is -- the TPM becomes
>> inaccessible. However, particularly in the case of migration with
>> shared storage I need to access the QCoW2 file to check whether on
>> the target I have the right key. And this happens very early during
>> qemu startup on the target machine. So that's where the file locking
>> on the block layer comes in. I want to serialize access to the QCoW2
>> so I don't read intermediate state on the migration-target host that
>> can occur if the source is currently writing TPM state data.
>>
>> This patch and the command line provided key along with the
>> encryption mode added in patch 10 in this series add to usability
>> and help prevent failures.
>>
>> Regards,
>>       Stefan
> Sure, it's a useful feature of validating the device early.
> But as I said above, it's useful for other devices besides
> TPM. However it introduces issues where state changes
> since guest keeps running (such as what you have described here).
>
> I think solving this in a way specific to TPM is a mistake.
> Passing key on command line does not mean you must use it
> immediately, this can still be done when guest starts running.
>
If I was not using the key immediately I could just drop this patch and 
the following one introducing the blob encryption and we would have to 
go with the QCoW2 encryption mode. Delaying the key usage then becomes 
equivalent to how QCoW2 is handling its encryption mode along with the 
problems related to user-friendliness / handling of missing or wrong 
keys described earlier.

> Further, the patchset seems to be split in
> a way that introduces support headaches and makes review
Patch 8 introduces file locking for bdrv. Patch 9 implements support for 
string TPM blobs inside a QCoW2 image and makes use of the locking. 
Patch 10 adds encryption support for the TPM blobs. What otherwise would 
be the logical split?
> harder, not easier. In this case, we will have to support
> both a broken mode (key supplied later) and a non-broken one
> (key supplied on init). It would be better to implement
> a single mode, in a single patch.
>
>
If we call QCoW2 encryption support the 'broken mode' and we want to do 
better than this then I do have to solve it in a TPM-specific way since 
Qemu otherwise does not support any better method (afaics).
QCoW2 encryption is there today and we get it for free. The only thing I 
could really do in patch 10 in this series is check whether the QCoW2 
image is encrypted and refuse to use it.
I find it contradicting what you said above.

Regards,
    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 13:06             ` Stefan Berger
@ 2011-09-07 13:16               ` Michael S. Tsirkin
  2011-09-07 13:56                 ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 13:16 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
> >>First: There are two ways to encrypt the data.
> >>
> >>One comes with the QCoW2 type of image and it comes for free. Set
> >>the encryption flag when creating the QCoW2 file and one has to
> >>provide a key to access the QCoW2. I found this mode problematic for
> >>users since it required me to go through the monitor every time I
> >>started the VM. Besides that the key is provided so late that all
> >>devices are already initialized and if the wrong key was provided
> >>the only thing the TPM can do is to go into shutdown mode since
> >>there is state on the QCoW2 but it cannot be decrypted. This also
> >>became problematic when doing migrations with libvirt for example
> >>and one was to have a wrong key/password installed on the target
> >>side -- graceful termination of the migration is impossible.

OK let's go back to this for a moment. Add a load
callback, access file there. On failure, return
an error. migration fails gracefully, and
management can retry, or migrate to another node,
or whatever.

What's the problem exactly?


-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-07 11:23           ` Michael S. Tsirkin
@ 2011-09-07 13:51             ` Stefan Berger
  2011-09-07 13:57               ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 13:51 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, serge

On 09/07/2011 07:23 AM, Michael S. Tsirkin wrote:
> On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
>> On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
>>> On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
>>>
>>> initstate_fd=<file descriptor>
>>> initstate_base64=on/off (or base64/bin if you really expect
>>> 	more formats in the future)
>>>
>>> and use qemu routines to get the fd so they can be
>>> passed through the monitor later ...
>>>
>> I suppose you mean monitor_get_fd(). That functions seems to only be
>> used by net.c so far and if  understand the chain of functions
>> correctly that are called with the monitor as parameter it helps in
>> hotplugging net devices ? For the TPM I would like to *not* have
>> support for hotplugging since that device is supposed to be soldered
>> to the motherboard and needs to be initialized through a command
>> sequence by the (v)BIOS, so it has to be present early on during
>> machine startup.
>>
>>    Stefan
> Fine, but let's reuse common functions and save code duplication,
> especially parsing functions.
>
When parsing the command line there's no Monitor being passed around. So 
in case of 'net' net_handle_fd_param() (net.c)  ends up not invoking 
monitor_get_fd() but the else branch where strtol() is used to convert 
the fd. Now I won't call net_handle_fd_param() but could introduce 
tpm_handle_fd_param() also calling strtol(). Though that would not make 
me call a common function but duplicating the code there... I don't know 
of another function handling the parsing of fd's. Is there one ? If not, 
I'll also just fall back to using strtol().

   Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 13:16               ` Michael S. Tsirkin
@ 2011-09-07 13:56                 ` Stefan Berger
  2011-09-07 14:10                   ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 13:56 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
>>>> First: There are two ways to encrypt the data.
>>>>
>>>> One comes with the QCoW2 type of image and it comes for free. Set
>>>> the encryption flag when creating the QCoW2 file and one has to
>>>> provide a key to access the QCoW2. I found this mode problematic for
>>>> users since it required me to go through the monitor every time I
>>>> started the VM. Besides that the key is provided so late that all
>>>> devices are already initialized and if the wrong key was provided
>>>> the only thing the TPM can do is to go into shutdown mode since
>>>> there is state on the QCoW2 but it cannot be decrypted. This also
>>>> became problematic when doing migrations with libvirt for example
>>>> and one was to have a wrong key/password installed on the target
>>>> side -- graceful termination of the migration is impossible.
> OK let's go back to this for a moment. Add a load
> callback, access file there. On failure, return
> an error. migration fails gracefully, and
> management can retry, or migrate to another node,
> or whatever.
>
> What's the problem exactly?
>
>
The switch-over from source to destination already happened when the key 
is finally passed and you just won't be able to access the QCoW2 in case 
the key was wrong. Similar problems occur when you start a VM with an 
encrypted QCoW2 image. The monitor will prompt you for the password and 
then you start the VM and if the password was wrong the OS just won't be 
able to access the image.

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state
  2011-09-07 13:51             ` Stefan Berger
@ 2011-09-07 13:57               ` Michael S. Tsirkin
  0 siblings, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 13:57 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, serge

On Wed, Sep 07, 2011 at 09:51:00AM -0400, Stefan Berger wrote:
> On 09/07/2011 07:23 AM, Michael S. Tsirkin wrote:
> >On Tue, Sep 06, 2011 at 10:45:34PM -0400, Stefan Berger wrote:
> >>On 09/04/2011 12:38 PM, Michael S. Tsirkin wrote:
> >>>On Thu, Sep 01, 2011 at 11:00:56PM -0400, Stefan Berger wrote:
> >>>
> >>>initstate_fd=<file descriptor>
> >>>initstate_base64=on/off (or base64/bin if you really expect
> >>>	more formats in the future)
> >>>
> >>>and use qemu routines to get the fd so they can be
> >>>passed through the monitor later ...
> >>>
> >>I suppose you mean monitor_get_fd(). That functions seems to only be
> >>used by net.c so far and if  understand the chain of functions
> >>correctly that are called with the monitor as parameter it helps in
> >>hotplugging net devices ? For the TPM I would like to *not* have
> >>support for hotplugging since that device is supposed to be soldered
> >>to the motherboard and needs to be initialized through a command
> >>sequence by the (v)BIOS, so it has to be present early on during
> >>machine startup.
> >>
> >>   Stefan
> >Fine, but let's reuse common functions and save code duplication,
> >especially parsing functions.
> >
> When parsing the command line there's no Monitor being passed
> around. So in case of 'net' net_handle_fd_param() (net.c)  ends up
> not invoking monitor_get_fd() but the else branch where strtol() is
> used to convert the fd. Now I won't call net_handle_fd_param() but
> could introduce tpm_handle_fd_param() also calling strtol(). Though
> that would not make me call a common function but duplicating the
> code there... I don't know of another function handling the parsing
> of fd's. Is there one ? If not, I'll also just fall back to using
> strtol().
> 
>   Stefan

We can create a common function and use that for net and tpm.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 13:56                 ` Stefan Berger
@ 2011-09-07 14:10                   ` Michael S. Tsirkin
  2011-09-07 14:25                     ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 14:10 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
> On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
> >>>>First: There are two ways to encrypt the data.
> >>>>
> >>>>One comes with the QCoW2 type of image and it comes for free. Set
> >>>>the encryption flag when creating the QCoW2 file and one has to
> >>>>provide a key to access the QCoW2. I found this mode problematic for
> >>>>users since it required me to go through the monitor every time I
> >>>>started the VM. Besides that the key is provided so late that all
> >>>>devices are already initialized and if the wrong key was provided
> >>>>the only thing the TPM can do is to go into shutdown mode since
> >>>>there is state on the QCoW2 but it cannot be decrypted. This also
> >>>>became problematic when doing migrations with libvirt for example
> >>>>and one was to have a wrong key/password installed on the target
> >>>>side -- graceful termination of the migration is impossible.
> >OK let's go back to this for a moment. Add a load
> >callback, access file there. On failure, return
> >an error. migration fails gracefully, and
> >management can retry, or migrate to another node,
> >or whatever.
> >
> >What's the problem exactly?
> >
> >
> The switch-over from source to destination already happened when the
> key is finally passed and you just won't be able to access the QCoW2
> in case the key was wrong.

This is exactly what happens with any kind of othe rmigration errror.
So fail migration, and source can get restarted if necessary.

> Similar problems occur when you start a
> VM with an encrypted QCoW2 image. The monitor will prompt you for
> the password and then you start the VM and if the password was wrong
> the OS just won't be able to access the image.
> 
>    Stefan

Why can't you verify the password?

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 14:10                   ` Michael S. Tsirkin
@ 2011-09-07 14:25                     ` Stefan Berger
  2011-09-07 14:35                       ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 14:25 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel,
	andreas.niederl, alevy, rrelyea, Serge E. Hallyn

On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
>> On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
>>> On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
>>>>>> First: There are two ways to encrypt the data.
>>>>>>
>>>>>> One comes with the QCoW2 type of image and it comes for free. Set
>>>>>> the encryption flag when creating the QCoW2 file and one has to
>>>>>> provide a key to access the QCoW2. I found this mode problematic for
>>>>>> users since it required me to go through the monitor every time I
>>>>>> started the VM. Besides that the key is provided so late that all
>>>>>> devices are already initialized and if the wrong key was provided
>>>>>> the only thing the TPM can do is to go into shutdown mode since
>>>>>> there is state on the QCoW2 but it cannot be decrypted. This also
>>>>>> became problematic when doing migrations with libvirt for example
>>>>>> and one was to have a wrong key/password installed on the target
>>>>>> side -- graceful termination of the migration is impossible.
>>> OK let's go back to this for a moment. Add a load
>>> callback, access file there. On failure, return
>>> an error. migration fails gracefully, and
>>> management can retry, or migrate to another node,
>>> or whatever.
>>>
>>> What's the problem exactly?
>>>
>>>
>> The switch-over from source to destination already happened when the
>> key is finally passed and you just won't be able to access the QCoW2
>> in case the key was wrong.
> This is exactly what happens with any kind of othe rmigration errror.
> So fail migration, and source can get restarted if necessary.
>
I guess I wanted to improve on this and catch user errors.
If we let migration fail then all you can do is try to terminate the VM 
on the destination and cold-start on the source.
>> Similar problems occur when you start a
>> VM with an encrypted QCoW2 image. The monitor will prompt you for
>> the password and then you start the VM and if the password was wrong
>> the OS just won't be able to access the image.
>>
>>     Stefan
> Why can't you verify the password?
>
I do verify the key/password in the TPM driver. If the driver cannot 
make sense of the contents of the QCoW2 due to wrong key I simply put 
the driver into failure mode. That's all I can do with encrypted QCoW2.

In case of a QCoW2 encrypted VM image it's different. There I guess the 
intelligence of what is good and bad data is only inside the OS.

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 14:25                     ` Stefan Berger
@ 2011-09-07 14:35                       ` Michael S. Tsirkin
  2011-09-07 15:06                         ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 14:35 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel,
	andreas.niederl, alevy, rrelyea, Serge E. Hallyn

On Wed, Sep 07, 2011 at 10:25:08AM -0400, Stefan Berger wrote:
> On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
> >>On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
> >>>On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
> >>>>>>First: There are two ways to encrypt the data.
> >>>>>>
> >>>>>>One comes with the QCoW2 type of image and it comes for free. Set
> >>>>>>the encryption flag when creating the QCoW2 file and one has to
> >>>>>>provide a key to access the QCoW2. I found this mode problematic for
> >>>>>>users since it required me to go through the monitor every time I
> >>>>>>started the VM. Besides that the key is provided so late that all
> >>>>>>devices are already initialized and if the wrong key was provided
> >>>>>>the only thing the TPM can do is to go into shutdown mode since
> >>>>>>there is state on the QCoW2 but it cannot be decrypted. This also
> >>>>>>became problematic when doing migrations with libvirt for example
> >>>>>>and one was to have a wrong key/password installed on the target
> >>>>>>side -- graceful termination of the migration is impossible.
> >>>OK let's go back to this for a moment. Add a load
> >>>callback, access file there. On failure, return
> >>>an error. migration fails gracefully, and
> >>>management can retry, or migrate to another node,
> >>>or whatever.
> >>>
> >>>What's the problem exactly?
> >>>
> >>>
> >>The switch-over from source to destination already happened when the
> >>key is finally passed and you just won't be able to access the QCoW2
> >>in case the key was wrong.
> >This is exactly what happens with any kind of othe rmigration errror.
> >So fail migration, and source can get restarted if necessary.
> >
> I guess I wanted to improve on this and catch user errors.
> If we let migration fail then all you can do is try to terminate the
> VM on the destination and cold-start on the source.

No, normally if migration fails VM is not started on destination,
and it can just continue on source.

> >>Similar problems occur when you start a
> >>VM with an encrypted QCoW2 image. The monitor will prompt you for
> >>the password and then you start the VM and if the password was wrong
> >>the OS just won't be able to access the image.
> >>
> >>    Stefan
> >Why can't you verify the password?
> >
> I do verify the key/password in the TPM driver. If the driver cannot
> make sense of the contents of the QCoW2 due to wrong key I simply
> put the driver into failure mode. That's all I can do with encrypted
> QCoW2.

You can return error from init script which will make qemu exit.

> In case of a QCoW2 encrypted VM image it's different. There I guess
> the intelligence of what is good and bad data is only inside the OS.
> 
>    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 14:35                       ` Michael S. Tsirkin
@ 2011-09-07 15:06                         ` Stefan Berger
  2011-09-07 15:16                           ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 15:06 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel,
	andreas.niederl, alevy, rrelyea, Serge E. Hallyn

On 09/07/2011 10:35 AM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 10:25:08AM -0400, Stefan Berger wrote:
>> On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
>>> On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
>>>> On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
>>>>> On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
>>>>>>>> First: There are two ways to encrypt the data.
>>>>>>>>
>>>>>>>> One comes with the QCoW2 type of image and it comes for free. Set
>>>>>>>> the encryption flag when creating the QCoW2 file and one has to
>>>>>>>> provide a key to access the QCoW2. I found this mode problematic for
>>>>>>>> users since it required me to go through the monitor every time I
>>>>>>>> started the VM. Besides that the key is provided so late that all
>>>>>>>> devices are already initialized and if the wrong key was provided
>>>>>>>> the only thing the TPM can do is to go into shutdown mode since
>>>>>>>> there is state on the QCoW2 but it cannot be decrypted. This also
>>>>>>>> became problematic when doing migrations with libvirt for example
>>>>>>>> and one was to have a wrong key/password installed on the target
>>>>>>>> side -- graceful termination of the migration is impossible.
>>>>> OK let's go back to this for a moment. Add a load
>>>>> callback, access file there. On failure, return
>>>>> an error. migration fails gracefully, and
>>>>> management can retry, or migrate to another node,
>>>>> or whatever.
>>>>>
>>>>> What's the problem exactly?
>>>>>
>>>>>
>>>> The switch-over from source to destination already happened when the
>>>> key is finally passed and you just won't be able to access the QCoW2
>>>> in case the key was wrong.
>>> This is exactly what happens with any kind of othe rmigration errror.
>>> So fail migration, and source can get restarted if necessary.
>>>
>> I guess I wanted to improve on this and catch user errors.
>> If we let migration fail then all you can do is try to terminate the
>> VM on the destination and cold-start on the source.
> No, normally if migration fails VM is not started on destination,
> and it can just continue on source.
>
When I had tried this in conjunction with encrypted QCoW2 the 
switch-over already had happened and the source had died. So a wrong key 
on the destination was fatal.
>>>> Similar problems occur when you start a
>>>> VM with an encrypted QCoW2 image. The monitor will prompt you for
>>>> the password and then you start the VM and if the password was wrong
>>>> the OS just won't be able to access the image.
>>>>
>>>>     Stefan
>>> Why can't you verify the password?
>>>
>> I do verify the key/password in the TPM driver. If the driver cannot
>> make sense of the contents of the QCoW2 due to wrong key I simply
>> put the driver into failure mode. That's all I can do with encrypted
>> QCoW2.
> You can return error from init script which will make qemu exit.
>
I can return an error code when the front- and backend interfaces are 
initialized, but that happens really early and the encyrption key 
entered through the monitor is not available at this point.

I also don't get a notification about when the key was entered. In case 
of QCoW2 encryption (and migration) I delay initialization until very 
late, basically when the VM accesses the tpm tis hardware emulation 
layer again (needs to be done this way I think to support block 
migration where I cannot even access the block device early on at all). 
Only then I find out that the key was wrong. I guess any other handling 
would require blockdev.c's invocation of monitor_read_bdrv_key_start() 
to be 'somehow' extended and ... do what ? loop until the correct 
password was entered?

    Stefan
>> In case of a QCoW2 encrypted VM image it's different. There I guess
>> the intelligence of what is good and bad data is only inside the OS.
>>
>>     Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 15:06                         ` Stefan Berger
@ 2011-09-07 15:16                           ` Michael S. Tsirkin
  2011-09-07 16:08                             ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 15:16 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel,
	andreas.niederl, alevy, rrelyea, Serge E. Hallyn

On Wed, Sep 07, 2011 at 11:06:42AM -0400, Stefan Berger wrote:
> On 09/07/2011 10:35 AM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 10:25:08AM -0400, Stefan Berger wrote:
> >>On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
> >>>On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
> >>>>On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
> >>>>>On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
> >>>>>>>>First: There are two ways to encrypt the data.
> >>>>>>>>
> >>>>>>>>One comes with the QCoW2 type of image and it comes for free. Set
> >>>>>>>>the encryption flag when creating the QCoW2 file and one has to
> >>>>>>>>provide a key to access the QCoW2. I found this mode problematic for
> >>>>>>>>users since it required me to go through the monitor every time I
> >>>>>>>>started the VM. Besides that the key is provided so late that all
> >>>>>>>>devices are already initialized and if the wrong key was provided
> >>>>>>>>the only thing the TPM can do is to go into shutdown mode since
> >>>>>>>>there is state on the QCoW2 but it cannot be decrypted. This also
> >>>>>>>>became problematic when doing migrations with libvirt for example
> >>>>>>>>and one was to have a wrong key/password installed on the target
> >>>>>>>>side -- graceful termination of the migration is impossible.
> >>>>>OK let's go back to this for a moment. Add a load
> >>>>>callback, access file there. On failure, return
> >>>>>an error. migration fails gracefully, and
> >>>>>management can retry, or migrate to another node,
> >>>>>or whatever.
> >>>>>
> >>>>>What's the problem exactly?
> >>>>>
> >>>>>
> >>>>The switch-over from source to destination already happened when the
> >>>>key is finally passed and you just won't be able to access the QCoW2
> >>>>in case the key was wrong.
> >>>This is exactly what happens with any kind of othe rmigration errror.
> >>>So fail migration, and source can get restarted if necessary.
> >>>
> >>I guess I wanted to improve on this and catch user errors.
> >>If we let migration fail then all you can do is try to terminate the
> >>VM on the destination and cold-start on the source.
> >No, normally if migration fails VM is not started on destination,
> >and it can just continue on source.
> >
> When I had tried this in conjunction with encrypted QCoW2 the
> switch-over already had happened and the source had died.

Giving continue command should bring it back.

> So a wrong key on the destination was fatal.

So it's a bug in the code then?

> >>>>Similar problems occur when you start a
> >>>>VM with an encrypted QCoW2 image. The monitor will prompt you for
> >>>>the password and then you start the VM and if the password was wrong
> >>>>the OS just won't be able to access the image.
> >>>>
> >>>>    Stefan
> >>>Why can't you verify the password?
> >>>
> >>I do verify the key/password in the TPM driver. If the driver cannot
> >>make sense of the contents of the QCoW2 due to wrong key I simply
> >>put the driver into failure mode. That's all I can do with encrypted
> >>QCoW2.
> >You can return error from init script which will make qemu exit.
> >
> I can return an error code when the front- and backend interfaces
> are initialized, but that happens really early and the encyrption
> key entered through the monitor is not available at this point.
>
> I also don't get a notification about when the key was entered. In
> case of QCoW2 encryption (and migration) I delay initialization
> until very late, basically when the VM accesses the tpm tis hardware
> emulation layer again (needs to be done this way I think to support
> block migration where I cannot even access the block device early on
> at all).

So it in the loadvm callback. This happens when guest is
stopped on source, so no need for locks.
On failure you return an error and migration fails
before destination is started. You can

> Only then I find out that the key was wrong. I guess any
> other handling would require blockdev.c's invocation of
> monitor_read_bdrv_key_start() to be 'somehow' extended and ... do
> what ? loop until the correct password was entered?

Return an error so vm start fails?

>    Stefan
> >>In case of a QCoW2 encrypted VM image it's different. There I guess
> >>the intelligence of what is good and bad data is only inside the OS.
> >>
> >>    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 15:16                           ` Michael S. Tsirkin
@ 2011-09-07 16:08                             ` Stefan Berger
  2011-09-07 18:49                               ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-07 16:08 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On 09/07/2011 11:16 AM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 11:06:42AM -0400, Stefan Berger wrote:
>> On 09/07/2011 10:35 AM, Michael S. Tsirkin wrote:
>>> On Wed, Sep 07, 2011 at 10:25:08AM -0400, Stefan Berger wrote:
>>>> On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
>>>>> On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
>>>>>> On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
>>>>>>> On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
>>>>>>>>>> First: There are two ways to encrypt the data.
>>>>>>>>>>
>>>>>>>>>> One comes with the QCoW2 type of image and it comes for free. Set
>>>>>>>>>> the encryption flag when creating the QCoW2 file and one has to
>>>>>>>>>> provide a key to access the QCoW2. I found this mode problematic for
>>>>>>>>>> users since it required me to go through the monitor every time I
>>>>>>>>>> started the VM. Besides that the key is provided so late that all
>>>>>>>>>> devices are already initialized and if the wrong key was provided
>>>>>>>>>> the only thing the TPM can do is to go into shutdown mode since
>>>>>>>>>> there is state on the QCoW2 but it cannot be decrypted. This also
>>>>>>>>>> became problematic when doing migrations with libvirt for example
>>>>>>>>>> and one was to have a wrong key/password installed on the target
>>>>>>>>>> side -- graceful termination of the migration is impossible.
>>>>>>> OK let's go back to this for a moment. Add a load
>>>>>>> callback, access file there. On failure, return
>>>>>>> an error. migration fails gracefully, and
>>>>>>> management can retry, or migrate to another node,
>>>>>>> or whatever.
>>>>>>>
>>>>>>> What's the problem exactly?
>>>>>>>
>>>>>>>
>>>>>> The switch-over from source to destination already happened when the
>>>>>> key is finally passed and you just won't be able to access the QCoW2
>>>>>> in case the key was wrong.
>>>>> This is exactly what happens with any kind of othe rmigration errror.
>>>>> So fail migration, and source can get restarted if necessary.
>>>>>
>>>> I guess I wanted to improve on this and catch user errors.
>>>> If we let migration fail then all you can do is try to terminate the
>>>> VM on the destination and cold-start on the source.
>>> No, normally if migration fails VM is not started on destination,
>>> and it can just continue on source.
>>>
>> When I had tried this in conjunction with encrypted QCoW2 the
>> switch-over already had happened and the source had died.
> Giving continue command should bring it back.
>
On the source? Qemu on the source didn't exist anymore.
>> So a wrong key on the destination was fatal.
> So it's a bug in the code then?
>
 From what I saw, yes. Migration is not complete until the passwords had 
been entered. Though the requirement for a correct password wasn't there 
before because Qemu just couldn't know which password is correct since 
it doesn't know what content in a VM image is correct -- just using the 
wrong key gives you content but it's of course not understandable.
>>>>>> Similar problems occur when you start a
>>>>>> VM with an encrypted QCoW2 image. The monitor will prompt you for
>>>>>> the password and then you start the VM and if the password was wrong
>>>>>> the OS just won't be able to access the image.
>>>>>>
>>>>>>     Stefan
>>>>> Why can't you verify the password?
>>>>>
>>>> I do verify the key/password in the TPM driver. If the driver cannot
>>>> make sense of the contents of the QCoW2 due to wrong key I simply
>>>> put the driver into failure mode. That's all I can do with encrypted
>>>> QCoW2.
>>> You can return error from init script which will make qemu exit.
>>>
>> I can return an error code when the front- and backend interfaces
>> are initialized, but that happens really early and the encyrption
>> key entered through the monitor is not available at this point.
>>
>> I also don't get a notification about when the key was entered. In
>> case of QCoW2 encryption (and migration) I delay initialization
>> until very late, basically when the VM accesses the tpm tis hardware
>> emulation layer again (needs to be done this way I think to support
>> block migration where I cannot even access the block device early on
>> at all).
> So it in the loadvm callback. This happens when guest is
> stopped on source, so no need for locks.
Two bigger cases here:

1) Encryption key passed via command line:
     - Migration with shared storage: When Qemu is initializing on the 
destination side I try to access the QCoW2 file. I do some basic tests 
to check whether a key was needed but none was given or whether some of 
the content could be read to confirm a valid key. This is done really 
early on during startup of Qemu on the destination side while or before 
actually the memory pages were transferred. Graceful termination was 
easily possible here.
     - Migration using block migration: During initialization I only see 
an empty QCoW2 file (created by libvirt). I terminate at this point and 
do another initialization later on which basically comes down to 
initializing upon access of the TPM TIS interface. At this point 
graceful termination wasn't possible anymore. There may be a possibility 
to do this in the loadvm callback, assuming block migration at that 
point has already finished, which I am not quite sure. Though along with 
case 2) below this would then end up in 3 different times for 
initialization of the emulation layer.

2) QCoW2 encryption:
     - This maps to the last case above. Also here graceful termination 
wasn't possible.

As for the loadvm callback: I have a note in my code that in case of 
QCoW2 encryption the key is not available, yet. So I even have to defer 
initialization further. In this case Qemu on the source machine will 
have terminated.

    Stefan


> On failure you return an error and migration fails
> before destination is started. You can
>

>> Only then I find out that the key was wrong. I guess any
>> other handling would require blockdev.c's invocation of
>> monitor_read_bdrv_key_start() to be 'somehow' extended and ... do
>> what ? loop until the correct password was entered?
> Return an error so vm start fails?
>
>>     Stefan
>>>> In case of a QCoW2 encrypted VM image it's different. There I guess
>>>> the intelligence of what is good and bad data is only inside the OS.
>>>>
>>>>     Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 16:08                             ` Stefan Berger
@ 2011-09-07 18:49                               ` Michael S. Tsirkin
  2011-09-08  0:31                                 ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 18:49 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel,
	andreas.niederl, alevy, rrelyea, Serge E. Hallyn

On Wed, Sep 07, 2011 at 12:08:22PM -0400, Stefan Berger wrote:
> On 09/07/2011 11:16 AM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 11:06:42AM -0400, Stefan Berger wrote:
> >>On 09/07/2011 10:35 AM, Michael S. Tsirkin wrote:
> >>>On Wed, Sep 07, 2011 at 10:25:08AM -0400, Stefan Berger wrote:
> >>>>On 09/07/2011 10:10 AM, Michael S. Tsirkin wrote:
> >>>>>On Wed, Sep 07, 2011 at 09:56:52AM -0400, Stefan Berger wrote:
> >>>>>>On 09/07/2011 09:16 AM, Michael S. Tsirkin wrote:
> >>>>>>>On Wed, Sep 07, 2011 at 09:06:05AM -0400, Stefan Berger wrote:
> >>>>>>>>>>First: There are two ways to encrypt the data.
> >>>>>>>>>>
> >>>>>>>>>>One comes with the QCoW2 type of image and it comes for free. Set
> >>>>>>>>>>the encryption flag when creating the QCoW2 file and one has to
> >>>>>>>>>>provide a key to access the QCoW2. I found this mode problematic for
> >>>>>>>>>>users since it required me to go through the monitor every time I
> >>>>>>>>>>started the VM. Besides that the key is provided so late that all
> >>>>>>>>>>devices are already initialized and if the wrong key was provided
> >>>>>>>>>>the only thing the TPM can do is to go into shutdown mode since
> >>>>>>>>>>there is state on the QCoW2 but it cannot be decrypted. This also
> >>>>>>>>>>became problematic when doing migrations with libvirt for example
> >>>>>>>>>>and one was to have a wrong key/password installed on the target
> >>>>>>>>>>side -- graceful termination of the migration is impossible.
> >>>>>>>OK let's go back to this for a moment. Add a load
> >>>>>>>callback, access file there. On failure, return
> >>>>>>>an error. migration fails gracefully, and
> >>>>>>>management can retry, or migrate to another node,
> >>>>>>>or whatever.
> >>>>>>>
> >>>>>>>What's the problem exactly?
> >>>>>>>
> >>>>>>>
> >>>>>>The switch-over from source to destination already happened when the
> >>>>>>key is finally passed and you just won't be able to access the QCoW2
> >>>>>>in case the key was wrong.
> >>>>>This is exactly what happens with any kind of othe rmigration errror.
> >>>>>So fail migration, and source can get restarted if necessary.
> >>>>>
> >>>>I guess I wanted to improve on this and catch user errors.
> >>>>If we let migration fail then all you can do is try to terminate the
> >>>>VM on the destination and cold-start on the source.
> >>>No, normally if migration fails VM is not started on destination,
> >>>and it can just continue on source.
> >>>
> >>When I had tried this in conjunction with encrypted QCoW2 the
> >>switch-over already had happened and the source had died.
> >Giving continue command should bring it back.
> >
> On the source? Qemu on the source didn't exist anymore.

didn't exist? Well, fix your management to not kill
until destination starts running then.
Really, all other devices have the same problem,
we can't solve it 100% but yes, we could
make it more robust. But there appears to be
nothing TPM specific here.

> >>So a wrong key on the destination was fatal.
> >So it's a bug in the code then?
> >
> From what I saw, yes. Migration is not complete until the passwords
> had been entered. Though the requirement for a correct password
> wasn't there before because Qemu just couldn't know which password
> is correct since it doesn't know what content in a VM image is
> correct -- just using the wrong key gives you content but it's of
> course not understandable.

OK, we covered that on irc - the issue is that monitor
on destination is inactive until migration is complete.
Yes we need to fix that but no, it's not a tpm only
problem.

> >>>>>>Similar problems occur when you start a
> >>>>>>VM with an encrypted QCoW2 image. The monitor will prompt you for
> >>>>>>the password and then you start the VM and if the password was wrong
> >>>>>>the OS just won't be able to access the image.
> >>>>>>
> >>>>>>    Stefan
> >>>>>Why can't you verify the password?
> >>>>>
> >>>>I do verify the key/password in the TPM driver. If the driver cannot
> >>>>make sense of the contents of the QCoW2 due to wrong key I simply
> >>>>put the driver into failure mode. That's all I can do with encrypted
> >>>>QCoW2.
> >>>You can return error from init script which will make qemu exit.
> >>>
> >>I can return an error code when the front- and backend interfaces
> >>are initialized, but that happens really early and the encyrption
> >>key entered through the monitor is not available at this point.
> >>
> >>I also don't get a notification about when the key was entered. In
> >>case of QCoW2 encryption (and migration) I delay initialization
> >>until very late, basically when the VM accesses the tpm tis hardware
> >>emulation layer again (needs to be done this way I think to support
> >>block migration where I cannot even access the block device early on
> >>at all).
> >So it in the loadvm callback. This happens when guest is
> >stopped on source, so no need for locks.
> Two bigger cases here:
> 
> 1) Encryption key passed via command line:
>     - Migration with shared storage: When Qemu is initializing on
> the destination side I try to access the QCoW2 file. I do some basic
> tests to check whether a key was needed but none was given or
> whether some of the content could be read to confirm a valid key.
> This is done really early on during startup of Qemu on the
> destination side while or before actually the memory pages were
> transferred. Graceful termination was easily possible here.
>     - Migration using block migration: During initialization I only
> see an empty QCoW2 file (created by libvirt). I terminate at this
> point and do another initialization later on which basically comes
> down to initializing upon access of the TPM TIS interface. At this
> point graceful termination wasn't possible anymore. There may be a
> possibility to do this in the loadvm callback, assuming block
> migration at that point has already finished, which I am not quite
> sure. Though along with case 2) below this would then end up in 3
> different times for initialization of the emulation layer.
> 
> 2) QCoW2 encryption:
>     - This maps to the last case above. Also here graceful
> termination wasn't possible.
> 
> As for the loadvm callback: I have a note in my code that in case of
> QCoW2 encryption the key is not available, yet. So I even have to
> defer initialization further. In this case Qemu on the source
> machine will have terminated.
> 
>    Stefan

The point is to decrypt when you start running on dest.
At that point source is stopped. On failure,
notify management and have it restart source.
On success, management can kill source qemu.

> >On failure you return an error and migration fails
> >before destination is started. You can
> >
> 
> >>Only then I find out that the key was wrong. I guess any
> >>other handling would require blockdev.c's invocation of
> >>monitor_read_bdrv_key_start() to be 'somehow' extended and ... do
> >>what ? loop until the correct password was entered?
> >Return an error so vm start fails?
> >
> >>    Stefan
> >>>>In case of a QCoW2 encrypted VM image it's different. There I guess
> >>>>the intelligence of what is good and bad data is only inside the OS.
> >>>>
> >>>>    Stefan
> 

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-02  2:23     ` Stefan Berger
  2011-09-04 16:58       ` Michael S. Tsirkin
@ 2011-09-07 18:55       ` Michael S. Tsirkin
  2011-09-08  0:16         ` Stefan Berger
  1 sibling, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-07 18:55 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
> >>An additional 'layer' for reading and writing the blobs to the underlying
> >>block storage is added. This layer encrypts the blobs for writing if a key is
> >>available. Similarly it decrypts the blobs after reading.

So a couple of further thoughts:
1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
   So I'd say it's worth supporting.
2. File backed nvram is interesting outside tpm.
   For example,vpd and chassis number for pci, eeprom emulation for network cards.
   Using a file per device might be inconvenient though.
   So please think of a format and API that will allow sections
   for use by different devices.
3. Home-grown file formats give us enough trouble in migration.
   Could this use one of the variants of ASN.1?
   There are portable libraries to read/write that, even.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-07 18:55       ` Michael S. Tsirkin
@ 2011-09-08  0:16         ` Stefan Berger
  2011-09-08 10:32           ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-08  0:16 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/07/2011 02:55 PM, Michael S. Tsirkin wrote:
> On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
>>>> An additional 'layer' for reading and writing the blobs to the underlying
>>>> block storage is added. This layer encrypts the blobs for writing if a key is
>>>> available. Similarly it decrypts the blobs after reading.
> So a couple of further thoughts:
> 1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
>     So I'd say it's worth supporting.
NFS via shared storage, yes, but not migration via Qemu's block 
migration mechanism. If snapshotting was supposed to be a feature to 
support then that's only possible via block storage (QCoW2 in particular).

Adding plain file support to the TPM code so it can store its 3 blobs 
into adds quite a bit of complexity to the code. The command line 
parameter that previously pointed to QCoW2 image file would probably 
have to point to a directory where files for the 3 blobs can be written 
into. Besides that, snapshotting would actually have to be prevented 
maybe through registering a (fake) file of other than QCoW2 type since 
the plain TPM files won't handle snapshotting correctly, either, and 
QEMU pretty much would have to be prevented from doing snapshotting at 
all. Maybe there's an API for this, but I don't know. Though why create 
this additional complexity? I don't mind relaxing the requirement of 
using a QCoW2 image and allowing for example RAW images (that then 
automatically prevent the snapshotting from happening) but the same code 
I now have would work for writing the blobs into it the single file.


> 2. File backed nvram is interesting outside tpm.
>     For example,vpd and chassis number for pci, eeprom emulation for network cards.
>     Using a file per device might be inconvenient though.
>     So please think of a format and API that will allow sections
>     for use by different devices.
Also here 'snapshotting' is the most 'demanding' feature of QEMU I would 
say. Snapshotting isn't easily supported outside of the block layer from 
what I understand. Once you are tied to the block layer you end up 
having to use images and those don't grow quite well. So other devices 
wanting to use those type of devices would need to know what the worst 
case sizes are for writing their state into -- unless an image format is 
created that can grow.

As for the format: Ideally all devices could write into one file, right? 
That would at least prevent too many files besides the VM's image file 
from floating around which presumably makes image management easier. 
Following the above, you add up all the worst case sizes the individual 
devices may need for their blobs and create an image with that capacity. 
Then you need some form of a (primitive?) directory that lets you write 
blobs into that storage. Assuming there were well defined names for 
those devices one could say for example store this blobs under the name 
'tpm-permanent-state' and later on load it under that name. The possible 
size of the directory would have to be considered as well... I do 
something like that for the TPM where I have up to 3 such blobs that I 
store.

The bad thing about the above is of course the need to know what the sum 
of all the worst case sizes is. So a growable image format would be 
quite good to have. I haven't followed the conversations much, but is 
that something QCoW3 would support?

Crazy idea: Is there a filesystem that one could use and mount a 
filesystem onto (some) sectors of an image? Again, the best format right 
now is QCoW2 for this (due to snapshotting suport) where one would have 
to be able to mount a filesystem onto the current snapshot's available 
sectors. Then at least the handling of blobs would become a lot easier. 
Though I doubt this would be possible without custom code and lots of 
development.


> 3. Home-grown file formats give us enough trouble in migration.
>     Could this use one of the variants of ASN.1?
>     There are portable libraries to read/write that, even.
>
I am not sure what 'this' refers to. What I am doing with the TPM is 
writing 3 independent blobs at certain offset into the QCoW2 block file. 
A directory in the first sector holds the offsets, sizes and crc32's of 
these (unencrypted) blobs.
I am not that familiar with ASN.1 except that from what I have seen it 
looks like a fairly terrible format needing an object language to create 
a parser from etc. not to mention the problems I had with snacc trying 
to compile the ASN.1 object language of an RFC...

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-07 18:49                               ` Michael S. Tsirkin
@ 2011-09-08  0:31                                 ` Stefan Berger
  2011-09-08 10:36                                   ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-08  0:31 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On 09/07/2011 02:49 PM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 12:08:22PM -0400, Stefan Berger wrote:
>> On 09/07/2011 11:16 AM, Michael S. Tsirkin wrote:
>>>
>>> So it's a bug in the code then?
>>>
>>  From what I saw, yes. Migration is not complete until the passwords
>> had been entered. Though the requirement for a correct password
>> wasn't there before because Qemu just couldn't know which password
>> is correct since it doesn't know what content in a VM image is
>> correct -- just using the wrong key gives you content but it's of
>> course not understandable.
> OK, we covered that on irc - the issue is that monitor
> on destination is inactive until migration is complete.
> Yes we need to fix that but no, it's not a tpm only
> problem.
I think the TPM is the first device that needs that password before the 
migration switch-over happens. The reason is that the TPM emulation 
layer needs the password/key to read the data from the QCoW2 to be able 
to initialize a device BEFORE the Qemu on the source side terminates 
thinking that the migration went ok. Previously an OS image that was 
'opened' with the wrong key/password would probably cause the OS to not 
be able to read the data and hopefully not destroy it by writing wrongly 
encrypted data into it -- QEMU wasn't able to detect whether the QCoW2 
encryption key was correct or not since it has not knowledge of the 
organization of the data inside the image.
[[You'd need some form of reference point, like a sector that when 
written to a hash is calculated over its data and that hash is written 
into a location in the image. If a wrong key is given and the sector's 
hash ends up being != the reference hash you could say the key is wrong.]]
>>>>>>>> Similar problems occur when you start a
>>>>>>>> VM with an encrypted QCoW2 image. The monitor will prompt you for
>>>>>>>> the password and then you start the VM and if the password was wrong
>>>>>>>> the OS just won't be able to access the image.
>>>>>>>>
>>>>>>>>     Stefan
>>>>>>> Why can't you verify the password?
>>>>>>>
>>>>>> I do verify the key/password in the TPM driver. If the driver cannot
>>>>>> make sense of the contents of the QCoW2 due to wrong key I simply
>>>>>> put the driver into failure mode. That's all I can do with encrypted
>>>>>> QCoW2.
>>>>> You can return error from init script which will make qemu exit.
>>>>>
>>>> I can return an error code when the front- and backend interfaces
>>>> are initialized, but that happens really early and the encyrption
>>>> key entered through the monitor is not available at this point.
>>>>
>>>> I also don't get a notification about when the key was entered. In
>>>> case of QCoW2 encryption (and migration) I delay initialization
>>>> until very late, basically when the VM accesses the tpm tis hardware
>>>> emulation layer again (needs to be done this way I think to support
>>>> block migration where I cannot even access the block device early on
>>>> at all).
>>> So it in the loadvm callback. This happens when guest is
>>> stopped on source, so no need for locks.
>> Two bigger cases here:
>>
>> 1) Encryption key passed via command line:
>>      - Migration with shared storage: When Qemu is initializing on
>> the destination side I try to access the QCoW2 file. I do some basic
>> tests to check whether a key was needed but none was given or
>> whether some of the content could be read to confirm a valid key.
>> This is done really early on during startup of Qemu on the
>> destination side while or before actually the memory pages were
>> transferred. Graceful termination was easily possible here.
>>      - Migration using block migration: During initialization I only
>> see an empty QCoW2 file (created by libvirt). I terminate at this
>> point and do another initialization later on which basically comes
>> down to initializing upon access of the TPM TIS interface. At this
>> point graceful termination wasn't possible anymore. There may be a
>> possibility to do this in the loadvm callback, assuming block
>> migration at that point has already finished, which I am not quite
>> sure. Though along with case 2) below this would then end up in 3
>> different times for initialization of the emulation layer.
>>
>> 2) QCoW2 encryption:
>>      - This maps to the last case above. Also here graceful
>> termination wasn't possible.
>>
>> As for the loadvm callback: I have a note in my code that in case of
>> QCoW2 encryption the key is not available, yet. So I even have to
>> defer initialization further. In this case Qemu on the source
>> machine will have terminated.
>>
>>     Stefan
> The point is to decrypt when you start running on dest.
When the monitor gets the key for the QCoW2 it would have to invoke the 
TPM driver code (callback) and return an error code if the key was found 
to be wrong and display an error message that libvirt could react to. 
Afaik none of the callback and error display logic  is in place. Is that 
something we could add later as an improvement?
> At that point source is stopped. On failure,
> notify management and have it restart source.
> On success, management can kill source qemu.
>
Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-08  0:16         ` Stefan Berger
@ 2011-09-08 10:32           ` Michael S. Tsirkin
  2011-09-08 12:11             ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-08 10:32 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Wed, Sep 07, 2011 at 08:16:27PM -0400, Stefan Berger wrote:
> On 09/07/2011 02:55 PM, Michael S. Tsirkin wrote:
> >On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
> >>>>An additional 'layer' for reading and writing the blobs to the underlying
> >>>>block storage is added. This layer encrypts the blobs for writing if a key is
> >>>>available. Similarly it decrypts the blobs after reading.
> >So a couple of further thoughts:
> >1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
> >    So I'd say it's worth supporting.
> NFS via shared storage, yes, but not migration via Qemu's block
> migration mechanism. If snapshotting was supposed to be a feature to
> support then that's only possible via block storage (QCoW2 in
> particular).

As disk has the same limitation, that sounds fine.
Let the user decide whether snapshoting is needed,
same as disk.

> Adding plain file support to the TPM code so it can store its 3
> blobs into adds quite a bit of complexity to the code. The command
> line parameter that previously pointed to QCoW2 image file would
> probably have to point to a directory where files for the 3 blobs
> can be written into. Besides that, snapshotting would actually have
> to be prevented maybe through registering a (fake) file of other
> than QCoW2 type since the plain TPM files won't handle snapshotting
> correctly, either, and QEMU pretty much would have to be prevented
> from doing snapshotting at all. Maybe there's an API for this, but I
> don't know. Though why create this additional complexity? I don't
> mind relaxing the requirement of using a QCoW2 image and allowing
> for example RAW images (that then automatically prevent the
> snapshotting from happening) but the same code I now have would work
> for writing the blobs into it the single file.

Right. Write all blobs into a single files at different
offsets, or something.

> >2. File backed nvram is interesting outside tpm.
> >    For example,vpd and chassis number for pci, eeprom emulation for network cards.
> >    Using a file per device might be inconvenient though.
> >    So please think of a format and API that will allow sections
> >    for use by different devices.
> Also here 'snapshotting' is the most 'demanding' feature of QEMU I
> would say. Snapshotting isn't easily supported outside of the block
> layer from what I understand. Once you are tied to the block layer
> you end up having to use images and those don't grow quite well. So
> other devices wanting to use those type of devices would need to
> know what the worst case sizes are for writing their state into --
> unless an image format is created that can grow.
> 
> As for the format: Ideally all devices could write into one file,
> right? That would at least prevent too many files besides the VM's
> image file from floating around which presumably makes image
> management easier. Following the above, you add up all the worst
> case sizes the individual devices may need for their blobs and
> create an image with that capacity. Then you need some form of a
> (primitive?) directory that lets you write blobs into that storage.
> Assuming there were well defined names for those devices one could
> say for example store this blobs under the name
> 'tpm-permanent-state' and later on load it under that name. The
> possible size of the directory would have to be considered as
> well... I do something like that for the TPM where I have up to 3
> such blobs that I store.
> 
> The bad thing about the above is of course the need to know what the
> sum of all the worst case sizes is.

A typical usecase I know about has prepared vpd/eeprom content.
We'll typically need a tool to get binary blobs and put that into the
file image.  That tool can do the necessary math.
We could also integrate this into qemu-img if we like.

> So a growable image format would
> be quite good to have. I haven't followed the conversations much,
> but is that something QCoW3 would support?

I don't follow - does TPM need a growable image format? Why?
Hardware typically has fixed amount of memory :)

> Crazy idea: Is there a filesystem that one could use and mount a
> filesystem onto (some) sectors of an image? Again, the best format
> right now is QCoW2 for this (due to snapshotting suport) where one
> would have to be able to mount a filesystem onto the current
> snapshot's available sectors. Then at least the handling of blobs
> would become a lot easier. Though I doubt this would be possible
> without custom code and lots of development.

Hmm, libguestfs can do all kind of smart stuff.
But we don't want qemu to depend on that.

> >3. Home-grown file formats give us enough trouble in migration.
> >    Could this use one of the variants of ASN.1?
> >    There are portable libraries to read/write that, even.
> >
> I am not sure what 'this' refers to. What I am doing with the TPM is
> writing 3 independent blobs at certain offset into the QCoW2 block
> file. A directory in the first sector holds the offsets, sizes and
> crc32's of these (unencrypted) blobs.

Right. It's the encoding of the directory that is custom,
and that bothers me. I'd prefer a format that is self-describing and
self-delimiting, give a way to inspect the data using external tools.

> I am not that familiar with ASN.1 except that from what I have seen
> it looks like a fairly terrible format needing an object language to
> create a parser from etc. not to mention the problems I had with
> snacc trying to compile the ASN.1 object language of an RFC...
> 
>    Stefan

Sorry about the confusion, we don't need the notation, I don't mean that.
I mean use a subset of the ASN.1 basic encoding
http://homepages.dcc.ufmg.br/~coelho/nm/asn.1.intro.pdf

So we could have a set of sequences, with an ascii string (a tag)
followed by an octet string (content).


-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer
  2011-09-08  0:31                                 ` Stefan Berger
@ 2011-09-08 10:36                                   ` Michael S. Tsirkin
  0 siblings, 0 replies; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-08 10:36 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, Anthony Liguori, anbang.ruan, qemu-devel, rrelyea, alevy,
	andreas.niederl, Serge E. Hallyn

On Wed, Sep 07, 2011 at 08:31:45PM -0400, Stefan Berger wrote:
> On 09/07/2011 02:49 PM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 12:08:22PM -0400, Stefan Berger wrote:
> >>On 09/07/2011 11:16 AM, Michael S. Tsirkin wrote:
> >>>
> >>>So it's a bug in the code then?
> >>>
> >> From what I saw, yes. Migration is not complete until the passwords
> >>had been entered. Though the requirement for a correct password
> >>wasn't there before because Qemu just couldn't know which password
> >>is correct since it doesn't know what content in a VM image is
> >>correct -- just using the wrong key gives you content but it's of
> >>course not understandable.
> >OK, we covered that on irc - the issue is that monitor
> >on destination is inactive until migration is complete.
> >Yes we need to fix that but no, it's not a tpm only
> >problem.
> I think the TPM is the first device that needs that password before
> the migration switch-over happens.

Yes. But we want the monitor on dest for other reasons,
for example to be able to check migration status.

> The reason is that the TPM
> emulation layer needs the password/key to read the data from the
> QCoW2 to be able to initialize a device BEFORE the Qemu on the
> source side terminates thinking that the migration went ok.
> Previously an OS image that was 'opened' with the wrong key/password
> would probably cause the OS to not be able to read the data and
> hopefully not destroy it by writing wrongly encrypted data into it
> -- QEMU wasn't able to detect whether the QCoW2 encryption key was
> correct or not since it has not knowledge of the organization of the
> data inside the image.
> [[You'd need some form of reference point, like a sector that when
> written to a hash is calculated over its data and that hash is
> written into a location in the image. If a wrong key is given and
> the sector's hash ends up being != the reference hash you could say
> the key is wrong.]]
> >>>>>>>>Similar problems occur when you start a
> >>>>>>>>VM with an encrypted QCoW2 image. The monitor will prompt you for
> >>>>>>>>the password and then you start the VM and if the password was wrong
> >>>>>>>>the OS just won't be able to access the image.
> >>>>>>>>
> >>>>>>>>    Stefan
> >>>>>>>Why can't you verify the password?
> >>>>>>>
> >>>>>>I do verify the key/password in the TPM driver. If the driver cannot
> >>>>>>make sense of the contents of the QCoW2 due to wrong key I simply
> >>>>>>put the driver into failure mode. That's all I can do with encrypted
> >>>>>>QCoW2.
> >>>>>You can return error from init script which will make qemu exit.
> >>>>>
> >>>>I can return an error code when the front- and backend interfaces
> >>>>are initialized, but that happens really early and the encyrption
> >>>>key entered through the monitor is not available at this point.
> >>>>
> >>>>I also don't get a notification about when the key was entered. In
> >>>>case of QCoW2 encryption (and migration) I delay initialization
> >>>>until very late, basically when the VM accesses the tpm tis hardware
> >>>>emulation layer again (needs to be done this way I think to support
> >>>>block migration where I cannot even access the block device early on
> >>>>at all).
> >>>So it in the loadvm callback. This happens when guest is
> >>>stopped on source, so no need for locks.
> >>Two bigger cases here:
> >>
> >>1) Encryption key passed via command line:
> >>     - Migration with shared storage: When Qemu is initializing on
> >>the destination side I try to access the QCoW2 file. I do some basic
> >>tests to check whether a key was needed but none was given or
> >>whether some of the content could be read to confirm a valid key.
> >>This is done really early on during startup of Qemu on the
> >>destination side while or before actually the memory pages were
> >>transferred. Graceful termination was easily possible here.
> >>     - Migration using block migration: During initialization I only
> >>see an empty QCoW2 file (created by libvirt). I terminate at this
> >>point and do another initialization later on which basically comes
> >>down to initializing upon access of the TPM TIS interface. At this
> >>point graceful termination wasn't possible anymore. There may be a
> >>possibility to do this in the loadvm callback, assuming block
> >>migration at that point has already finished, which I am not quite
> >>sure. Though along with case 2) below this would then end up in 3
> >>different times for initialization of the emulation layer.
> >>
> >>2) QCoW2 encryption:
> >>     - This maps to the last case above. Also here graceful
> >>termination wasn't possible.
> >>
> >>As for the loadvm callback: I have a note in my code that in case of
> >>QCoW2 encryption the key is not available, yet. So I even have to
> >>defer initialization further. In this case Qemu on the source
> >>machine will have terminated.
> >>
> >>    Stefan
> >The point is to decrypt when you start running on dest.
> When the monitor gets the key for the QCoW2 it would have to invoke
> the TPM driver code (callback) and return an error code if the key
> was found to be wrong and display an error message that libvirt
> could react to.
> Afaik none of the callback and error display logic
> is in place.
> Is that something we could add later as an improvement?

What we do for errors detected at destination is fail migration.
This logic is in place as migration can fail
when we get a state we don't recognize.
management is prepared to handle that already.

> >At that point source is stopped. On failure,
> >notify management and have it restart source.
> >On success, management can kill source qemu.
> >
> Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-08 10:32           ` Michael S. Tsirkin
@ 2011-09-08 12:11             ` Stefan Berger
  2011-09-08 13:16               ` Michael S. Tsirkin
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-08 12:11 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/08/2011 06:32 AM, Michael S. Tsirkin wrote:
> On Wed, Sep 07, 2011 at 08:16:27PM -0400, Stefan Berger wrote:
>> On 09/07/2011 02:55 PM, Michael S. Tsirkin wrote:
>>> On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
>>>>>> An additional 'layer' for reading and writing the blobs to the underlying
>>>>>> block storage is added. This layer encrypts the blobs for writing if a key is
>>>>>> available. Similarly it decrypts the blobs after reading.
>>> So a couple of further thoughts:
>>> 1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
>>>     So I'd say it's worth supporting.
>> NFS via shared storage, yes, but not migration via Qemu's block
>> migration mechanism. If snapshotting was supposed to be a feature to
>> support then that's only possible via block storage (QCoW2 in
>> particular).
> As disk has the same limitation, that sounds fine.
> Let the user decide whether snapshoting is needed,
> same as disk.
>
>> Adding plain file support to the TPM code so it can store its 3
>> blobs into adds quite a bit of complexity to the code. The command
>> line parameter that previously pointed to QCoW2 image file would
>> probably have to point to a directory where files for the 3 blobs
>> can be written into. Besides that, snapshotting would actually have
>> to be prevented maybe through registering a (fake) file of other
>> than QCoW2 type since the plain TPM files won't handle snapshotting
>> correctly, either, and QEMU pretty much would have to be prevented
>> from doing snapshotting at all. Maybe there's an API for this, but I
>> don't know. Though why create this additional complexity? I don't
>> mind relaxing the requirement of using a QCoW2 image and allowing
>> for example RAW images (that then automatically prevent the
>> snapshotting from happening) but the same code I now have would work
>> for writing the blobs into it the single file.
> Right. Write all blobs into a single files at different
> offsets, or something.

That's exactly what I am doing already. Just that I am doing this with 
Qemu's BlockStorage (bdrv)  writing to sectors rather than seek()ing in 
files. To avoid more complexity I'd rather not introduce more code 
handling plain files but rely on all the image formats that qemu already 
supports and that give features like encryption (QCoW2 only), 
snapshotting (QCoW2 only) and block migration (presumably all of them). 
Plain files offer none of that. Devices that need to write their state 
to persistent storage really have to aim for doing this through Qemu's 
bdrv since they will otherwise be the ones killing the snapshot feature. 
TPM certainly doesn't want to be one of them. If the user doesn't want 
snapshotting to be supported since his VM image files are not QCoW2 type 
of files, just create a raw image file for the TPM's persistent state 
and bdrv will automatically prevent snapshotting. The point is that the 
TPM code now using the bdrv layer works with any image format already.

>>> 2. File backed nvram is interesting outside tpm.
>>>     For example,vpd and chassis number for pci, eeprom emulation for network cards.
>>>     Using a file per device might be inconvenient though.
>>>     So please think of a format and API that will allow sections
>>>     for use by different devices.
>> Also here 'snapshotting' is the most 'demanding' feature of QEMU I
>> would say. Snapshotting isn't easily supported outside of the block
>> layer from what I understand. Once you are tied to the block layer
>> you end up having to use images and those don't grow quite well. So
>> other devices wanting to use those type of devices would need to
>> know what the worst case sizes are for writing their state into --
>> unless an image format is created that can grow.
>>
>> As for the format: Ideally all devices could write into one file,
>> right? That would at least prevent too many files besides the VM's
>> image file from floating around which presumably makes image
>> management easier. Following the above, you add up all the worst
>> case sizes the individual devices may need for their blobs and
>> create an image with that capacity. Then you need some form of a
>> (primitive?) directory that lets you write blobs into that storage.
>> Assuming there were well defined names for those devices one could
>> say for example store this blobs under the name
>> 'tpm-permanent-state' and later on load it under that name. The
>> possible size of the directory would have to be considered as
>> well... I do something like that for the TPM where I have up to 3
>> such blobs that I store.
>>
>> The bad thing about the above is of course the need to know what the
>> sum of all the worst case sizes is.
> A typical usecase I know about has prepared vpd/eeprom content.
> We'll typically need a tool to get binary blobs and put that into the
> file image.  That tool can do the necessary math.
> We could also integrate this into qemu-img if we like.
>
>> So a growable image format would
>> be quite good to have. I haven't followed the conversations much,
>> but is that something QCoW3 would support?
> I don't follow - does TPM need a growable image format? Why?
> Hardware typically has fixed amount of memory :)
Ideally the user wouldn't have to worry about creating the single file 
for persistent storage for all the devices at all but Qemu could 
'somehow' do this.
Assume the user starts the VM with a device having an EEPROM. Now that 
device has the need for 10k of persistent storage. So somehow with the 
limitations of images that don't grow you have to have created an image 
of at least 10k a priori. Later the user adds another device to the same 
VM that needs 40k of persistent storage. What now? Dispose the old image 
with the EPPROM data and create a new image with at least 50k to hold 
both their data? Or add another image with just 40k to hold that 
device's persistent state? I'd rather have the 10k image grow to 50k and 
accommodate both state blobs...

>>> 3. Home-grown file formats give us enough trouble in migration.
>>>     Could this use one of the variants of ASN.1?
>>>     There are portable libraries to read/write that, even.
>>>
>> I am not sure what 'this' refers to. What I am doing with the TPM is
>> writing 3 independent blobs at certain offset into the QCoW2 block
>> file. A directory in the first sector holds the offsets, sizes and
>> crc32's of these (unencrypted) blobs.
> Right. It's the encoding of the directory that is custom,
> and that bothers me. I'd prefer a format that is self-describing and
> self-delimiting, give a way to inspect the data using external tools.
Nothing would prevent us from defining a data structure for that 
directory as long as that data structure accommodates all use cases of 
today and especially tomorrow :-).

>> I am not that familiar with ASN.1 except that from what I have seen
>> it looks like a fairly terrible format needing an object language to
>> create a parser from etc. not to mention the problems I had with
>> snacc trying to compile the ASN.1 object language of an RFC...
>>
>>     Stefan
> Sorry about the confusion, we don't need the notation, I don't mean that.
> I mean use a subset of the ASN.1 basic encoding
> http://homepages.dcc.ufmg.br/~coelho/nm/asn.1.intro.pdf
>
> So we could have a set of sequences, with an ascii string (a tag)
> followed by an octet string (content).
>
>
I think the data layout in the image should be in such format that you 
don't have to re-write the whole content of the image if a blob is 
stored. I think a directory at the beginning could solve this. To make 
it simple one probably would need to know how big the 'directory' could 
be otherwise one has to get into allocation of sectors so that once the 
directory was to grow beyond 512 bytes that one would know where its 
next data are written into. The same is true for the devices' data 
blobs. If one knows the sizes of all the blobs one can lay them out to 
start and end at specific offsets in the image. And knowing the size of 
all the blobs helps in creating the image of correct size.
Well, all this is a work-around for not having a 'filesystem'.

     Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-08 12:11             ` Stefan Berger
@ 2011-09-08 13:16               ` Michael S. Tsirkin
  2011-09-08 15:27                 ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Michael S. Tsirkin @ 2011-09-08 13:16 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On Thu, Sep 08, 2011 at 08:11:00AM -0400, Stefan Berger wrote:
> On 09/08/2011 06:32 AM, Michael S. Tsirkin wrote:
> >On Wed, Sep 07, 2011 at 08:16:27PM -0400, Stefan Berger wrote:
> >>On 09/07/2011 02:55 PM, Michael S. Tsirkin wrote:
> >>>On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
> >>>>>>An additional 'layer' for reading and writing the blobs to the underlying
> >>>>>>block storage is added. This layer encrypts the blobs for writing if a key is
> >>>>>>available. Similarly it decrypts the blobs after reading.
> >>>So a couple of further thoughts:
> >>>1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
> >>>    So I'd say it's worth supporting.
> >>NFS via shared storage, yes, but not migration via Qemu's block
> >>migration mechanism. If snapshotting was supposed to be a feature to
> >>support then that's only possible via block storage (QCoW2 in
> >>particular).
> >As disk has the same limitation, that sounds fine.
> >Let the user decide whether snapshoting is needed,
> >same as disk.
> >
> >>Adding plain file support to the TPM code so it can store its 3
> >>blobs into adds quite a bit of complexity to the code. The command
> >>line parameter that previously pointed to QCoW2 image file would
> >>probably have to point to a directory where files for the 3 blobs
> >>can be written into. Besides that, snapshotting would actually have
> >>to be prevented maybe through registering a (fake) file of other
> >>than QCoW2 type since the plain TPM files won't handle snapshotting
> >>correctly, either, and QEMU pretty much would have to be prevented
> >>from doing snapshotting at all. Maybe there's an API for this, but I
> >>don't know. Though why create this additional complexity? I don't
> >>mind relaxing the requirement of using a QCoW2 image and allowing
> >>for example RAW images (that then automatically prevent the
> >>snapshotting from happening) but the same code I now have would work
> >>for writing the blobs into it the single file.
> >Right. Write all blobs into a single files at different
> >offsets, or something.
> 
> That's exactly what I am doing already. Just that I am doing this
> with Qemu's BlockStorage (bdrv)  writing to sectors rather than
> seek()ing in files. To avoid more complexity I'd rather not
> introduce more code handling plain files but rely on all the image
> formats that qemu already supports and that give features like
> encryption (QCoW2 only), snapshotting (QCoW2 only) and block
> migration (presumably all of them). Plain files offer none of that.
> Devices that need to write their state to persistent storage really
> have to aim for doing this through Qemu's bdrv since they will
> otherwise be the ones killing the snapshot feature. TPM certainly
> doesn't want to be one of them. If the user doesn't want
> snapshotting to be supported since his VM image files are not QCoW2
> type of files, just create a raw image file for the TPM's persistent
> state and bdrv will automatically prevent snapshotting. The point is
> that the TPM code now using the bdrv layer works with any image
> format already.

Ah, that's fine then. I had an impression there was a qcow only
limitation, not sure what in code gave me that impression.

> >>>2. File backed nvram is interesting outside tpm.
> >>>    For example,vpd and chassis number for pci, eeprom emulation for network cards.
> >>>    Using a file per device might be inconvenient though.
> >>>    So please think of a format and API that will allow sections
> >>>    for use by different devices.
> >>Also here 'snapshotting' is the most 'demanding' feature of QEMU I
> >>would say. Snapshotting isn't easily supported outside of the block
> >>layer from what I understand. Once you are tied to the block layer
> >>you end up having to use images and those don't grow quite well. So
> >>other devices wanting to use those type of devices would need to
> >>know what the worst case sizes are for writing their state into --
> >>unless an image format is created that can grow.
> >>
> >>As for the format: Ideally all devices could write into one file,
> >>right? That would at least prevent too many files besides the VM's
> >>image file from floating around which presumably makes image
> >>management easier. Following the above, you add up all the worst
> >>case sizes the individual devices may need for their blobs and
> >>create an image with that capacity. Then you need some form of a
> >>(primitive?) directory that lets you write blobs into that storage.
> >>Assuming there were well defined names for those devices one could
> >>say for example store this blobs under the name
> >>'tpm-permanent-state' and later on load it under that name. The
> >>possible size of the directory would have to be considered as
> >>well... I do something like that for the TPM where I have up to 3
> >>such blobs that I store.
> >>
> >>The bad thing about the above is of course the need to know what the
> >>sum of all the worst case sizes is.
> >A typical usecase I know about has prepared vpd/eeprom content.
> >We'll typically need a tool to get binary blobs and put that into the
> >file image.  That tool can do the necessary math.
> >We could also integrate this into qemu-img if we like.
> >
> >>So a growable image format would
> >>be quite good to have. I haven't followed the conversations much,
> >>but is that something QCoW3 would support?
> >I don't follow - does TPM need a growable image format? Why?
> >Hardware typically has fixed amount of memory :)
> Ideally the user wouldn't have to worry about creating the single
> file for persistent storage for all the devices at all but Qemu
> could 'somehow' do this.
> Assume the user starts the VM with a device having an EEPROM. Now
> that device has the need for 10k of persistent storage. So somehow
> with the limitations of images that don't grow you have to have
> created an image of at least 10k a priori. Later the user adds
> another device to the same VM that needs 40k of persistent storage.
> What now? Dispose the old image with the EPPROM data and create a
> new image with at least 50k to hold both their data? Or add another
> image with just 40k to hold that device's persistent state? I'd
> rather have the 10k image grow to 50k and accommodate both state
> blobs...

I see, yes, might be useful. But even without that,
simple users - without hotplug - will be able to have
a single file with all data, and advanced users will
be able to have a file per device. Not ideal
but I think manageable.

> >>>3. Home-grown file formats give us enough trouble in migration.
> >>>    Could this use one of the variants of ASN.1?
> >>>    There are portable libraries to read/write that, even.
> >>>
> >>I am not sure what 'this' refers to. What I am doing with the TPM is
> >>writing 3 independent blobs at certain offset into the QCoW2 block
> >>file. A directory in the first sector holds the offsets, sizes and
> >>crc32's of these (unencrypted) blobs.

By the way, why do we checksum data? Should be optional?

> >Right. It's the encoding of the directory that is custom,
> >and that bothers me. I'd prefer a format that is self-describing and
> >self-delimiting, give a way to inspect the data using external tools.
> Nothing would prevent us from defining a data structure for that
> directory as long as that data structure accommodates all use cases
> of today and especially tomorrow :-).

Right, so please give thought to the proposal of using a
subset of BER.

> >>I am not that familiar with ASN.1 except that from what I have seen
> >>it looks like a fairly terrible format needing an object language to
> >>create a parser from etc. not to mention the problems I had with
> >>snacc trying to compile the ASN.1 object language of an RFC...
> >>
> >>    Stefan
> >Sorry about the confusion, we don't need the notation, I don't mean that.
> >I mean use a subset of the ASN.1 basic encoding
> >http://homepages.dcc.ufmg.br/~coelho/nm/asn.1.intro.pdf
> >
> >So we could have a set of sequences, with an ascii string (a tag)
> >followed by an octet string (content).
> >
> >
> I think the data layout in the image should be in such format that
> you don't have to re-write the whole content of the image if a blob
> is stored.

With predefined blob size, we can use octet strings and not
have to rewrite anything, find the right octet and change it inplace.

> I think a directory at the beginning could solve this.

It could, but it's not needed for that.

> To make it simple one probably would need to know how big the
> 'directory' could be otherwise one has to get into allocation of
> sectors so that once the directory was to grow beyond 512 bytes that
> one would know where its next data are written into. The same is
> true for the devices' data blobs. If one knows the sizes of all the
> blobs one can lay them out to start and end at specific offsets in
> the image. And knowing the size of all the blobs helps in creating
> the image of correct size.
> Well, all this is a work-around for not having a 'filesystem'.
> 
>     Stefan
> 

Sounds like overkill. A sequence with tags in DER format is much easier.

-- 
MST

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption
  2011-09-08 13:16               ` Michael S. Tsirkin
@ 2011-09-08 15:27                 ` Stefan Berger
  0 siblings, 0 replies; 75+ messages in thread
From: Stefan Berger @ 2011-09-08 15:27 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: chrisw, anbang.ruan, qemu-devel, rrelyea, alevy, andreas.niederl, serge

On 09/08/2011 09:16 AM, Michael S. Tsirkin wrote:
> On Thu, Sep 08, 2011 at 08:11:00AM -0400, Stefan Berger wrote:
>> On 09/08/2011 06:32 AM, Michael S. Tsirkin wrote:
>>> On Wed, Sep 07, 2011 at 08:16:27PM -0400, Stefan Berger wrote:
>>>> On 09/07/2011 02:55 PM, Michael S. Tsirkin wrote:
>>>>> On Thu, Sep 01, 2011 at 10:23:51PM -0400, Stefan Berger wrote:
>>>>>>>> An additional 'layer' for reading and writing the blobs to the underlying
>>>>>>>> block storage is added. This layer encrypts the blobs for writing if a key is
>>>>>>>> available. Similarly it decrypts the blobs after reading.
>>>>> So a couple of further thoughts:
>>>>> 1. Raw storage should work too, and with e.g. NFS migration will be fine, right?
>>>>>     So I'd say it's worth supporting.
>>>> NFS via shared storage, yes, but not migration via Qemu's block
>>>> migration mechanism. If snapshotting was supposed to be a feature to
>>>> support then that's only possible via block storage (QCoW2 in
>>>> particular).
>>> As disk has the same limitation, that sounds fine.
>>> Let the user decide whether snapshoting is needed,
>>> same as disk.
>>>
>>>> Adding plain file support to the TPM code so it can store its 3
>>>> blobs into adds quite a bit of complexity to the code. The command
>>>> line parameter that previously pointed to QCoW2 image file would
>>>> probably have to point to a directory where files for the 3 blobs
>>>> can be written into. Besides that, snapshotting would actually have
>>>> to be prevented maybe through registering a (fake) file of other
>>>> than QCoW2 type since the plain TPM files won't handle snapshotting
>>>> correctly, either, and QEMU pretty much would have to be prevented
>>> >from doing snapshotting at all. Maybe there's an API for this, but I
>>>> don't know. Though why create this additional complexity? I don't
>>>> mind relaxing the requirement of using a QCoW2 image and allowing
>>>> for example RAW images (that then automatically prevent the
>>>> snapshotting from happening) but the same code I now have would work
>>>> for writing the blobs into it the single file.
>>> Right. Write all blobs into a single files at different
>>> offsets, or something.
>> That's exactly what I am doing already. Just that I am doing this
>> with Qemu's BlockStorage (bdrv)  writing to sectors rather than
>> seek()ing in files. To avoid more complexity I'd rather not
>> introduce more code handling plain files but rely on all the image
>> formats that qemu already supports and that give features like
>> encryption (QCoW2 only), snapshotting (QCoW2 only) and block
>> migration (presumably all of them). Plain files offer none of that.
>> Devices that need to write their state to persistent storage really
>> have to aim for doing this through Qemu's bdrv since they will
>> otherwise be the ones killing the snapshot feature. TPM certainly
>> doesn't want to be one of them. If the user doesn't want
>> snapshotting to be supported since his VM image files are not QCoW2
>> type of files, just create a raw image file for the TPM's persistent
>> state and bdrv will automatically prevent snapshotting. The point is
>> that the TPM code now using the bdrv layer works with any image
>> format already.
> Ah, that's fine then. I had an impression there was a qcow only
> limitation, not sure what in code gave me that impression.
Hm, currently I force the image to be a QCoW2.

     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;
     }

I can remove this and we should be fine.
>>>>> 2. File backed nvram is interesting outside tpm.
>>>>>     For example,vpd and chassis number for pci, eeprom emulation for network cards.
>>>>>     Using a file per device might be inconvenient though.
>>>>>     So please think of a format and API that will allow sections
>>>>>     for use by different devices.
>>>> Also here 'snapshotting' is the most 'demanding' feature of QEMU I
>>>> would say. Snapshotting isn't easily supported outside of the block
>>>> layer from what I understand. Once you are tied to the block layer
>>>> you end up having to use images and those don't grow quite well. So
>>>> other devices wanting to use those type of devices would need to
>>>> know what the worst case sizes are for writing their state into --
>>>> unless an image format is created that can grow.
>>>>
>>>> As for the format: Ideally all devices could write into one file,
>>>> right? That would at least prevent too many files besides the VM's
>>>> image file from floating around which presumably makes image
>>>> management easier. Following the above, you add up all the worst
>>>> case sizes the individual devices may need for their blobs and
>>>> create an image with that capacity. Then you need some form of a
>>>> (primitive?) directory that lets you write blobs into that storage.
>>>> Assuming there were well defined names for those devices one could
>>>> say for example store this blobs under the name
>>>> 'tpm-permanent-state' and later on load it under that name. The
>>>> possible size of the directory would have to be considered as
>>>> well... I do something like that for the TPM where I have up to 3
>>>> such blobs that I store.
>>>>
>>>> The bad thing about the above is of course the need to know what the
>>>> sum of all the worst case sizes is.
>>> A typical usecase I know about has prepared vpd/eeprom content.
>>> We'll typically need a tool to get binary blobs and put that into the
>>> file image.  That tool can do the necessary math.
>>> We could also integrate this into qemu-img if we like.
>>>
>>>> So a growable image format would
>>>> be quite good to have. I haven't followed the conversations much,
>>>> but is that something QCoW3 would support?
>>> I don't follow - does TPM need a growable image format? Why?
>>> Hardware typically has fixed amount of memory :)
>> Ideally the user wouldn't have to worry about creating the single
>> file for persistent storage for all the devices at all but Qemu
>> could 'somehow' do this.
>> Assume the user starts the VM with a device having an EEPROM. Now
>> that device has the need for 10k of persistent storage. So somehow
>> with the limitations of images that don't grow you have to have
>> created an image of at least 10k a priori. Later the user adds
>> another device to the same VM that needs 40k of persistent storage.
>> What now? Dispose the old image with the EPPROM data and create a
>> new image with at least 50k to hold both their data? Or add another
>> image with just 40k to hold that device's persistent state? I'd
>> rather have the 10k image grow to 50k and accommodate both state
>> blobs...
> I see, yes, might be useful. But even without that,
> simple users - without hotplug - will be able to have
> a single file with all data, and advanced users will
> be able to have a file per device. Not ideal
> but I think manageable.
>
As long as the management software keeps everything in one place (dir 
with subdirs?) it should be manageable. User just need to remember to 
pass on several files then for a single VM.
>>>>> 3. Home-grown file formats give us enough trouble in migration.
>>>>>     Could this use one of the variants of ASN.1?
>>>>>     There are portable libraries to read/write that, even.
>>>>>
>>>> I am not sure what 'this' refers to. What I am doing with the TPM is
>>>> writing 3 independent blobs at certain offset into the QCoW2 block
>>>> file. A directory in the first sector holds the offsets, sizes and
>>>> crc32's of these (unencrypted) blobs.
> By the way, why do we checksum data? Should be optional?
>
It lets one detect corruption.
In case encryption is being used for the individual blobs (that's NOT 
the QCoW2 native encryption but the one I added and that works for RAW 
images as well) I can use this crc32 after trying to decrypt the data. 
If the crc32 doesn't match the expected one it was either the wrong key 
or the data is corrupted. At least I can react to it and tell the user 
that the data could not be decrypted or are corrupted and terminate Qemu 
rather than having the device go into panic/shutdown mode on the bad state.

>>>> I am not that familiar with ASN.1 except that from what I have seen
>>>> it looks like a fairly terrible format needing an object language to
>>>> create a parser from etc. not to mention the problems I had with
>>>> snacc trying to compile the ASN.1 object language of an RFC...
>>>>
>>>>     Stefan
>>> Sorry about the confusion, we don't need the notation, I don't mean that.
>>> I mean use a subset of the ASN.1 basic encoding
>>> http://homepages.dcc.ufmg.br/~coelho/nm/asn.1.intro.pdf
>>>
>>> So we could have a set of sequences, with an ascii string (a tag)
>>> followed by an octet string (content).
>>>
>>>
>> I think the data layout in the image should be in such format that
>> you don't have to re-write the whole content of the image if a blob
>> is stored.
> With predefined blob size, we can use octet strings and not
> have to rewrite anything, find the right octet and change it inplace.
>
>> I think a directory at the beginning could solve this.
> It could, but it's not needed for that.
>
>> To make it simple one probably would need to know how big the
>> 'directory' could be otherwise one has to get into allocation of
>> sectors so that once the directory was to grow beyond 512 bytes that
>> one would know where its next data are written into. The same is
>> true for the devices' data blobs. If one knows the sizes of all the
>> blobs one can lay them out to start and end at specific offsets in
>> the image. And knowing the size of all the blobs helps in creating
>> the image of correct size.
>> Well, all this is a work-around for not having a 'filesystem'.
>>
>>      Stefan
>>
> Sounds like overkill. A sequence with tags in DER format is much easier.
>
What data should be encoded using DER?

- indication whether encryption was used on all blobs
- a sequence of
     - name of the blob
     - worst-case size of the blob
     - actual size of the blob
     - crc32 of the blob
     - the actual blob
?

    Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
@ 2011-09-09 19:28   ` Paul Moore
  0 siblings, 0 replies; 75+ messages in thread
From: Paul Moore @ 2011-09-09 19:28 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On Wednesday, August 31, 2011 10:35:53 AM Stefan Berger wrote:
> Index: qemu-git/hw/tpm_tis.c
> ===================================================================
> --- /dev/null
> +++ qemu-git/hw/tpm_tis.c
> @@ -0,0 +1,841 @@
> +/*
> + * tpm_tis.c - QEMU emulator for a 1.2 TPM with TIS interface
> + *
> + * Copyright (C) 2006,2010 IBM Corporation
> + *
> + * Author: Stefan Berger <stefanb@us.ibm.com>
> + *         David Safford <safford@us.ibm.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + *
> + * Implementation of the TIS interface according to specs at
> + *
> https://www.trustedcomputinggroup.org/groups/pc_client/TCG_PCClientTPMSpeci
> fication_1-20_1-00_FINAL.pdf 
> + *
> + */

The above link isn't working for me, has it moved or is my timing just really 
bad?  I believe I've found the spec, as well as an updated 1.21 version, on 
the TCG website but the URL is different.  Does it make more sense just to 
point people at the TCG website and provide them the name/version of the 
specification?  If all else fails, keeping a copy in the docs/specs directory 
may not be a terrible idea ...

I know it is a nit, and I'm not trying to be difficult, I'm just trying to get 
up to speed on the TPM details and noticed the link was bad.

> +/*
> + * Write a value to a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */

Since I've already started this email, one more thing ... I've come to realize 
there are quite a few specification documents on the TCG website; I think it 
might be helpful to reference the specification/version along with the page 
number(s) just to remove any ambiguity. 

> +static void tis_mem_writel_intern(void *opaque, target_phys_addr_t addr,
> +                                  uint32_t val, bool hw_access)

-- 
paul moore
virtualization @ redhat

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver Stefan Berger
  2011-09-01 17:20   ` Michael S. Tsirkin
@ 2011-09-09 21:13   ` Paul Moore
  2011-09-11 16:45     ` Stefan Berger
  1 sibling, 1 reply; 75+ messages in thread
From: Paul Moore @ 2011-09-09 21:13 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On Wednesday, August 31, 2011 10:35:54 AM Stefan Berger wrote:
> Index: qemu-git/hw/tpm_tis.c
> ===================================================================
> --- qemu-git.orig/hw/tpm_tis.c
> +++ qemu-git/hw/tpm_tis.c
> @@ -6,6 +6,8 @@
>   * Author: Stefan Berger <stefanb@us.ibm.com>
>   *         David Safford <safford@us.ibm.com>
>   *
> + * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
> + *
>   * This program is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU General Public License as
>   * published by the Free Software Foundation, version 2 of the
> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
>   err_exit:
>      return -1;
>  }
> +
> +/* persistent state handling */
> +
> +static void tis_pre_save(void *opaque)
> +{
> +    TPMState *s = opaque;
> +    uint8_t locty = s->active_locty;

Is it safe to read s->active_locty without the state_lock?  I'm not sure at 
this point but I saw it being protected by the lock elsewhere ...

If the state_lock does not protect all of the structure, it might be nice to 
add some comments in the structure declaration explaining what fields are 
protected by the state_lock and which are not.

> +    qemu_mutex_lock(&s->state_lock);
> +
> +    /* wait for outstanding requests to complete */
> +    if (IS_VALID_LOCTY(locty) && s->loc[locty].state == STATE_EXECUTION) {
> +        if (!s->be_driver->ops->job_for_main_thread) {
> +            qemu_cond_wait(&s->from_tpm_cond, &s->state_lock);
> +        } else {
> +            while (s->loc[locty].state == STATE_EXECUTION) {
> +                qemu_mutex_unlock(&s->state_lock);
> +
> +                s->be_driver->ops->job_for_main_thread(NULL);
> +                usleep(10000);
> +
> +                qemu_mutex_lock(&s->state_lock);

Hmm, this may be right, but it looks dangerous to me; can the active_locty 
change while the state_lock is dropped?  What about loc[locty].state?

> +            }
> +        }
> +    }
> +
> +#ifdef DEBUG_TIS_SR
> +    fprintf(stderr,
> +            "tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = %d\n",
> +            s->loc[0].r_offset, s->loc[0].w_offset);
> +    if (s->loc[0].r_offset) {
> +        tis_dump_state(opaque, 0);
> +    }
> +#endif
> +
> +    qemu_mutex_unlock(&s->state_lock);
> +
> +    /* copy current active read or write buffer into the buffer
> +       written to disk */
> +    if (IS_VALID_LOCTY(locty)) {
> +        switch (s->loc[locty].state) {

More concerns about loc[locty].state without the state_lock.

> +        case STATE_RECEPTION:
> +            memcpy(s->buf,
> +                   s->loc[locty].w_buffer.buffer,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].w_buffer.size));
> +            s->offset = s->loc[locty].w_offset;

Same thing, just different fields ...

> +        break;
> +        case STATE_COMPLETION:
> +            memcpy(s->buf,
> +                   s->loc[locty].r_buffer.buffer,
> +                   MIN(sizeof(s->buf),
> +                       s->loc[locty].r_buffer.size));
> +            s->offset = s->loc[locty].r_offset;

Again ...

> +        break;
> +        default:
> +            /* leak nothing */
> +            memset(s->buf, 0x0, sizeof(s->buf));

Maybe?

> +        break;
> +        }
> +    }
> +
> +    s->be_driver->ops->save_volatile_data();
> +}

-- 
paul moore
virtualization @ redhat

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-09-09 21:13   ` Paul Moore
@ 2011-09-11 16:45     ` Stefan Berger
  2011-09-12 21:16       ` Paul Moore
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-11 16:45 UTC (permalink / raw)
  To: Paul Moore
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On 09/09/2011 05:13 PM, Paul Moore wrote:
> On Wednesday, August 31, 2011 10:35:54 AM Stefan Berger wrote:
>> Index: qemu-git/hw/tpm_tis.c
>> ===================================================================
>> --- qemu-git.orig/hw/tpm_tis.c
>> +++ qemu-git/hw/tpm_tis.c
>> @@ -6,6 +6,8 @@
>>    * Author: Stefan Berger<stefanb@us.ibm.com>
>>    *         David Safford<safford@us.ibm.com>
>>    *
>> + * Xen 4 support: Andrease Niederl<andreas.niederl@iaik.tugraz.at>
>> + *
>>    * This program is free software; you can redistribute it and/or
>>    * modify it under the terms of the GNU General Public License as
>>    * published by the Free Software Foundation, version 2 of the
>> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
>>    err_exit:
>>       return -1;
>>   }
>> +
>> +/* persistent state handling */
>> +
>> +static void tis_pre_save(void *opaque)
>> +{
>> +    TPMState *s = opaque;
>> +    uint8_t locty = s->active_locty;
> Is it safe to read s->active_locty without the state_lock?  I'm not sure at
> this point but I saw it being protected by the lock elsewhere ...
It cannot change anymore since no vCPU is in the TPM TIS emulation layer 
anymore but all we're doing is wait for the last outstanding command to 
be returned to use from the TPM thread.
I don't mind putting this reading into the critical section, though, 
just to have it be consistent.

> If the state_lock does not protect all of the structure, it might be nice to
> add some comments in the structure declaration explaining what fields are
> protected by the state_lock and which are not.
>
>> +    qemu_mutex_lock(&s->state_lock);
>> +
>> +    /* wait for outstanding requests to complete */
>> +    if (IS_VALID_LOCTY(locty)&&  s->loc[locty].state == STATE_EXECUTION) {
>> +        if (!s->be_driver->ops->job_for_main_thread) {
>> +            qemu_cond_wait(&s->from_tpm_cond,&s->state_lock);
>> +        } else {
>> +            while (s->loc[locty].state == STATE_EXECUTION) {
>> +                qemu_mutex_unlock(&s->state_lock);
>> +
>> +                s->be_driver->ops->job_for_main_thread(NULL);
>> +                usleep(10000);
>> +
>> +                qemu_mutex_lock(&s->state_lock);
> Hmm, this may be right, but it looks dangerous to me; can the active_locty
> change while the state_lock is dropped?  What about loc[locty].state?
This is correct since at this time the VM is not executing anymore, so 
no vCPU can be in the TPM TIS emulation code anymore, but we're waiting 
for the last outstanding TPM command finish processing in the TPM thread 
(to have it's response 'caught' and stored as part of the TPM TIS 
state). The locking is against the thread at this point that may change 
the .state variable, although I don't think it would be necessary to 
hold the lock there at all except for in the case where the condition is 
being waited for in the other else branch.
>> +            }
>> +        }
>> +    }
>> +
>> +#ifdef DEBUG_TIS_SR
>> +    fprintf(stderr,
>> +            "tpm_tis: suspend: locty 0 : r_offset = %d, w_offset = %d\n",
>> +            s->loc[0].r_offset, s->loc[0].w_offset);
>> +    if (s->loc[0].r_offset) {
>> +        tis_dump_state(opaque, 0);
>> +    }
>> +#endif
>> +
>> +    qemu_mutex_unlock(&s->state_lock);
>> +
>> +    /* copy current active read or write buffer into the buffer
>> +       written to disk */
>> +    if (IS_VALID_LOCTY(locty)) {
>> +        switch (s->loc[locty].state) {
> More concerns about loc[locty].state without the state_lock.
>
The section you are quoting here is further down in the same function 
that prepares the TPM TIS for state serialization before final 
migration/suspend. At this point we have caught the last outstanding 
response from the TPM thread and that thread will not process any more 
commands at this point (queuing of commands it not possible with TPM TIS 
but strictly sending a single request to  it, have it processed, getting 
that response -- so the thread will be idle). Also since no more vCPU is 
in the TPM TIS emulation layer the state cannot change anymore. Again, 
also here I can have the critical section extended over this area.
>> +        case STATE_RECEPTION:
>> +            memcpy(s->buf,
>> +                   s->loc[locty].w_buffer.buffer,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].w_buffer.size));
>> +            s->offset = s->loc[locty].w_offset;
> Same thing, just different fields ...
>
>> +        break;
>> +        case STATE_COMPLETION:
>> +            memcpy(s->buf,
>> +                   s->loc[locty].r_buffer.buffer,
>> +                   MIN(sizeof(s->buf),
>> +                       s->loc[locty].r_buffer.size));
>> +            s->offset = s->loc[locty].r_offset;
> Again ...
Ok, I can move that single qemu_mutex_unlock(&s->state_lock) above to 
after the switch() though I don't think it is necessary in this case due 
the state the emulation is in. Though I agree that the code 'looks' more 
correct.
>> +        break;
>> +        default:
>> +            /* leak nothing */
>> +            memset(s->buf, 0x0, sizeof(s->buf));
> Maybe?
>
What do you mean?
This command just makes sure that no previous response still stored in 
the TPM TIS buffer is being stored as part of the TPM TIS state 
serialization.

Thanks for the review.

    Stefan
>> +        break;
>> +        }
>> +    }
>> +
>> +    s->be_driver->ops->save_volatile_data();
>> +}

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-09-11 16:45     ` Stefan Berger
@ 2011-09-12 21:16       ` Paul Moore
  2011-09-12 23:37         ` Stefan Berger
  0 siblings, 1 reply; 75+ messages in thread
From: Paul Moore @ 2011-09-12 21:16 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On Sunday, September 11, 2011 12:45:05 PM Stefan Berger wrote:
> On 09/09/2011 05:13 PM, Paul Moore wrote:
> > On Wednesday, August 31, 2011 10:35:54 AM Stefan Berger wrote:
> >> Index: qemu-git/hw/tpm_tis.c
> >> ===================================================================
> >> --- qemu-git.orig/hw/tpm_tis.c
> >> +++ qemu-git/hw/tpm_tis.c
> >> @@ -6,6 +6,8 @@
> >> 
> >>    * Author: Stefan Berger<stefanb@us.ibm.com>
> >>    *         David Safford<safford@us.ibm.com>
> >>    *
> >> 
> >> + * Xen 4 support: Andrease Niederl<andreas.niederl@iaik.tugraz.at>
> >> + *
> >> 
> >>    * This program is free software; you can redistribute it and/or
> >>    * modify it under the terms of the GNU General Public License as
> >>    * published by the Free Software Foundation, version 2 of the
> >> 
> >> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
> >> 
> >>    err_exit:
> >>       return -1;
> >>   
> >>   }
> >> 
> >> +
> >> +/* persistent state handling */
> >> +
> >> +static void tis_pre_save(void *opaque)
> >> +{
> >> +    TPMState *s = opaque;
> >> +    uint8_t locty = s->active_locty;
> > 
> > Is it safe to read s->active_locty without the state_lock?  I'm not sure
> > at this point but I saw it being protected by the lock elsewhere ...
> It cannot change anymore since no vCPU is in the TPM TIS emulation layer
> anymore but all we're doing is wait for the last outstanding command to
> be returned to use from the TPM thread.
> I don't mind putting this reading into the critical section, though,
> just to have it be consistent.

[Dropping the rest of the comments since they all cover the same issue]

I'm a big fan of consistency, especially when it comes to locking; 
inconsistent lock usage can lead to confusion and that is almost never good. 

If we need a lock here because there is the potential for an outstanding TPM 
command, then I vote for locking in this function just as you would in any 
other.  However, if we really don't need locks here because the outstanding 
TPM command will have _no_ effect on the TPMState or any related structure, 
then just do away with the lock completely and make of note of it in the 
function explaining why.

-- 
paul moore
virtualization @ redhat

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-09-12 21:16       ` Paul Moore
@ 2011-09-12 23:37         ` Stefan Berger
  2011-09-13 12:13           ` Paul Moore
  0 siblings, 1 reply; 75+ messages in thread
From: Stefan Berger @ 2011-09-12 23:37 UTC (permalink / raw)
  To: Paul Moore
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On 09/12/2011 05:16 PM, Paul Moore wrote:
> On Sunday, September 11, 2011 12:45:05 PM Stefan Berger wrote:
>> On 09/09/2011 05:13 PM, Paul Moore wrote:
>>> On Wednesday, August 31, 2011 10:35:54 AM Stefan Berger wrote:
>>>> Index: qemu-git/hw/tpm_tis.c
>>>> ===================================================================
>>>> --- qemu-git.orig/hw/tpm_tis.c
>>>> +++ qemu-git/hw/tpm_tis.c
>>>> @@ -6,6 +6,8 @@
>>>>
>>>>     * Author: Stefan Berger<stefanb@us.ibm.com>
>>>>     *         David Safford<safford@us.ibm.com>
>>>>     *
>>>>
>>>> + * Xen 4 support: Andrease Niederl<andreas.niederl@iaik.tugraz.at>
>>>> + *
>>>>
>>>>     * This program is free software; you can redistribute it and/or
>>>>     * modify it under the terms of the GNU General Public License as
>>>>     * published by the Free Software Foundation, version 2 of the
>>>>
>>>> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
>>>>
>>>>     err_exit:
>>>>        return -1;
>>>>
>>>>    }
>>>>
>>>> +
>>>> +/* persistent state handling */
>>>> +
>>>> +static void tis_pre_save(void *opaque)
>>>> +{
>>>> +    TPMState *s = opaque;
>>>> +    uint8_t locty = s->active_locty;
>>> Is it safe to read s->active_locty without the state_lock?  I'm not sure
>>> at this point but I saw it being protected by the lock elsewhere ...
>> It cannot change anymore since no vCPU is in the TPM TIS emulation layer
>> anymore but all we're doing is wait for the last outstanding command to
>> be returned to use from the TPM thread.
>> I don't mind putting this reading into the critical section, though,
>> just to have it be consistent.
> [Dropping the rest of the comments since they all cover the same issue]
>
> I'm a big fan of consistency, especially when it comes to locking;
> inconsistent lock usage can lead to confusion and that is almost never good.
>
> If we need a lock here because there is the potential for an outstanding TPM
> command, then I vote for locking in this function just as you would in any
> other.  However, if we really don't need locks here because the outstanding
> TPM command will have _no_ effect on the TPMState or any related structure,
> then just do away with the lock completely and make of note of it in the
> function explaining why.
>
Let's give the consistency argument the favor and extend the locking 
over those parts that usually also get locked.

Regards,
     Stefan

^ permalink raw reply	[flat|nested] 75+ messages in thread

* Re: [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver
  2011-09-12 23:37         ` Stefan Berger
@ 2011-09-13 12:13           ` Paul Moore
  0 siblings, 0 replies; 75+ messages in thread
From: Paul Moore @ 2011-09-13 12:13 UTC (permalink / raw)
  To: Stefan Berger
  Cc: chrisw, anbang.ruan, qemu-devel, andreas.niederl, alevy, rrelyea, serge

On Monday, September 12, 2011 07:37:25 PM Stefan Berger wrote:
> On 09/12/2011 05:16 PM, Paul Moore wrote:
> > On Sunday, September 11, 2011 12:45:05 PM Stefan Berger wrote:
> >> On 09/09/2011 05:13 PM, Paul Moore wrote:
> >>> On Wednesday, August 31, 2011 10:35:54 AM Stefan Berger wrote:
> >>>> Index: qemu-git/hw/tpm_tis.c
> >>>> ==================================================================
> >>>> =
> >>>> --- qemu-git.orig/hw/tpm_tis.c
> >>>> +++ qemu-git/hw/tpm_tis.c
> >>>> @@ -6,6 +6,8 @@
> >>>> 
> >>>>     * Author: Stefan Berger<stefanb@us.ibm.com>
> >>>>     *         David Safford<safford@us.ibm.com>
> >>>>     *
> >>>> 
> >>>> + * Xen 4 support: Andrease
> >>>> Niederl<andreas.niederl@iaik.tugraz.at>
> >>>> + *
> >>>> 
> >>>>     * This program is free software; you can redistribute it
> >>>>     and/or
> >>>>     * modify it under the terms of the GNU General Public
> >>>>     License as
> >>>>     * published by the Free Software Foundation, version 2 of
> >>>>     the
> >>>> 
> >>>> @@ -839,3 +841,167 @@ static int tis_init(ISADevice *dev)
> >>>> 
> >>>>     err_exit:
> >>>>        return -1;
> >>>>    
> >>>>    }
> >>>> 
> >>>> +
> >>>> +/* persistent state handling */
> >>>> +
> >>>> +static void tis_pre_save(void *opaque)
> >>>> +{
> >>>> +    TPMState *s = opaque;
> >>>> +    uint8_t locty = s->active_locty;
> >>> 
> >>> Is it safe to read s->active_locty without the state_lock?  I'm not
> >>> sure at this point but I saw it being protected by the lock
> >>> elsewhere ...>> 
> >> It cannot change anymore since no vCPU is in the TPM TIS emulation
> >> layer
> >> anymore but all we're doing is wait for the last outstanding command
> >> to
> >> be returned to use from the TPM thread.
> >> I don't mind putting this reading into the critical section, though,
> >> just to have it be consistent.
> > 
> > [Dropping the rest of the comments since they all cover the same issue]
> > 
> > I'm a big fan of consistency, especially when it comes to locking;
> > inconsistent lock usage can lead to confusion and that is almost never
> > good.
> > 
> > If we need a lock here because there is the potential for an outstanding
> > TPM command, then I vote for locking in this function just as you would
> > in any other.  However, if we really don't need locks here because the
> > outstanding TPM command will have _no_ effect on the TPMState or any
> > related structure, then just do away with the lock completely and make
> > of note of it in the function explaining why.
> 
> Let's give the consistency argument the favor and extend the locking
> over those parts that usually also get locked.

Great, thanks.

-- 
paul moore
virtualization @ redhat

^ permalink raw reply	[flat|nested] 75+ messages in thread

end of thread, other threads:[~2011-09-13 12:13 UTC | newest]

Thread overview: 75+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-08-31 14:35 [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 01/14] Support for TPM command line options Stefan Berger
2011-09-01 17:14   ` Michael S. Tsirkin
2011-09-02  1:01     ` Stefan Berger
2011-09-04 16:29       ` Michael S. Tsirkin
2011-09-04 16:50       ` Michael S. Tsirkin
2011-09-01 18:14   ` Michael S. Tsirkin
2011-09-02  1:02     ` Stefan Berger
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 02/14] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
2011-09-09 19:28   ` Paul Moore
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 03/14] Add persistent state handling to TPM TIS frontend driver Stefan Berger
2011-09-01 17:20   ` Michael S. Tsirkin
2011-09-02  1:12     ` Stefan Berger
2011-09-09 21:13   ` Paul Moore
2011-09-11 16:45     ` Stefan Berger
2011-09-12 21:16       ` Paul Moore
2011-09-12 23:37         ` Stefan Berger
2011-09-13 12:13           ` Paul Moore
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 04/14] Add tpm_tis driver to build process Stefan Berger
2011-09-01 17:23   ` Michael S. Tsirkin
2011-09-02  1:16     ` Stefan Berger
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 05/14] Add a debug register Stefan Berger
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 06/14] Add a TPM backend skeleton implementation Stefan Berger
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 07/14] Implementation of the libtpms-based backend Stefan Berger
2011-09-01 17:27   ` Michael S. Tsirkin
2011-09-02  1:24     ` Stefan Berger
2011-09-04 16:27       ` Michael S. Tsirkin
2011-08-31 14:35 ` [Qemu-devel] [PATCH V8 08/14] Introduce file lock for the block layer Stefan Berger
2011-09-01 17:32   ` Michael S. Tsirkin
2011-09-02  1:53     ` Stefan Berger
2011-09-04 19:32       ` Michael S. Tsirkin
2011-09-06 23:55         ` Stefan Berger
2011-09-07 11:18           ` Michael S. Tsirkin
2011-09-07 13:06             ` Stefan Berger
2011-09-07 13:16               ` Michael S. Tsirkin
2011-09-07 13:56                 ` Stefan Berger
2011-09-07 14:10                   ` Michael S. Tsirkin
2011-09-07 14:25                     ` Stefan Berger
2011-09-07 14:35                       ` Michael S. Tsirkin
2011-09-07 15:06                         ` Stefan Berger
2011-09-07 15:16                           ` Michael S. Tsirkin
2011-09-07 16:08                             ` Stefan Berger
2011-09-07 18:49                               ` Michael S. Tsirkin
2011-09-08  0:31                                 ` Stefan Berger
2011-09-08 10:36                                   ` Michael S. Tsirkin
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 09/14] Add block storage support for libtpms based TPM backend Stefan Berger
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 10/14] Encrypt state blobs using AES CBC encryption Stefan Berger
2011-09-01 19:26   ` Michael S. Tsirkin
2011-09-02  2:23     ` Stefan Berger
2011-09-04 16:58       ` Michael S. Tsirkin
2011-09-07  0:32         ` Stefan Berger
2011-09-07 11:59           ` Michael S. Tsirkin
2011-09-07 18:55       ` Michael S. Tsirkin
2011-09-08  0:16         ` Stefan Berger
2011-09-08 10:32           ` Michael S. Tsirkin
2011-09-08 12:11             ` Stefan Berger
2011-09-08 13:16               ` Michael S. Tsirkin
2011-09-08 15:27                 ` Stefan Berger
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 11/14] Experimental support for block migrating TPMs state Stefan Berger
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 12/14] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 13/14] Add a TPM backend null driver implementation Stefan Berger
2011-09-01 17:40   ` Michael S. Tsirkin
2011-09-02  2:41     ` Stefan Berger
2011-09-04 16:42       ` Michael S. Tsirkin
2011-08-31 14:36 ` [Qemu-devel] [PATCH V8 14/14] Allow to provide inital TPM state Stefan Berger
2011-09-01 18:10   ` Michael S. Tsirkin
2011-09-01 19:01     ` Michael S. Tsirkin
2011-09-02  3:00     ` Stefan Berger
2011-09-04 16:38       ` Michael S. Tsirkin
2011-09-07  2:45         ` Stefan Berger
2011-09-07 11:23           ` Michael S. Tsirkin
2011-09-07 13:51             ` Stefan Berger
2011-09-07 13:57               ` Michael S. Tsirkin
2011-09-01 18:12 ` [Qemu-devel] [PATCH V8 00/14] Qemu Trusted Platform Module (TPM) integration Michael S. Tsirkin
2011-09-02  3:02   ` Stefan Berger

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.