* [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration
@ 2011-07-06 16:34 Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 01/13] Support for TPM command line options Stefan Berger
` (12 more replies)
0 siblings, 13 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, 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:
x86_64:
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-0.5.1-7.x86_64.rpm
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-devel-0.5.1-7.x86_64.rpm
i686:
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-0.5.1-7.i686.rpm
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/x86_64/os/Packages/libtpms-devel-0.5.1-7.i686.rpm
Source rpm:
http://download.fedora.redhat.com/pub/fedora/linux/development/rawhide/source/SRPMS/libtpms-0.5.1-7.src.rpm
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! :-)).
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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 01/13] Support for TPM command line options
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 02/13] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
` (11 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm.diff --]
[-- Type: text/plain, Size: 20950 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
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
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 | 76 +++++++++++++++
monitor.c | 10 ++
qemu-config.c | 46 +++++++++
qemu-options.hx | 80 ++++++++++++++++
tpm.c | 278 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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
@@ -1707,6 +1707,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"
@@ -2436,6 +2437,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;
@@ -3077,6 +3086,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);
@@ -3318,6 +3333,9 @@ int main(int argc, char **argv, char **e
main_loop();
quit_timers();
net_cleanup();
+#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
@@ -469,6 +469,50 @@ static QemuOptsList qemu_machine_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,
@@ -484,6 +528,8 @@ static QemuOptsList *vm_config_groups[32
#endif
&qemu_option_rom_opts,
&qemu_machine_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,76 @@
+/*
+ * 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 11
+
+#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/Makefile.target
===================================================================
--- qemu-git.orig/Makefile.target
+++ qemu-git/Makefile.target
@@ -198,6 +198,7 @@ obj-y += rwhandler.o
obj-$(CONFIG_KVM) += kvm.o kvm-all.o
obj-$(CONFIG_NO_KVM) += kvm-stub.o
LIBS+=-lz
+obj-y += tpm.o
QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
QEMU_CFLAGS += $(VNC_SASL_CFLAGS)
Index: qemu-git/tpm.c
===================================================================
--- /dev/null
+++ qemu-git/tpm.c
@@ -0,0 +1,278 @@
+/*
+ * 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, "%7s %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) {
+ if (!(id = qemu_opts_id(opts))) {
+ 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
@@ -41,6 +41,7 @@
#include "sysemu.h"
#include "blockdev.h"
#include "ui/qemu-spice.h"
+#include "tpm.h"
/* output Bochs bios info messages */
//#define DEBUG_BIOS
@@ -60,7 +61,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)
@@ -1157,6 +1158,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"
@@ -3105,6 +3106,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
@@ -1353,6 +1353,8 @@ show device tree
show qdev device model list
@item info roms
show roms
+@item info tpm
+show the TPM devices
@end table
ETEXI
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V6 02/13] Add TPM (frontend) hardware interface (TPM TIS) to Qemu
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 01/13] Support for TPM command line options Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 03/13] Add persistent state handling to TPM TIS frontend driver Stefan Berger
` (10 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_tis.diff --]
[-- Type: text/plain, Size: 27665 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 | 839 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 839 insertions(+)
Index: qemu-git/hw/tpm_tis.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_tis.c
@@ -0,0 +1,839 @@
+/*
+ * 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",
+ __FUNCTION__);
+#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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 03/13] Add persistent state handling to TPM TIS frontend driver
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 01/13] Support for TPM command line options Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 02/13] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 04/13] Add tpm_tis driver to build process Stefan Berger
` (9 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_tis_persist.diff --]
[-- Type: text/plain, Size: 6566 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
@@ -837,3 +839,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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 04/13] Add tpm_tis driver to build process
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (2 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 03/13] Add persistent state handling to TPM TIS frontend driver Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 05/13] Add a debug register Stefan Berger
` (8 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_build.diff --]
[-- Type: text/plain, Size: 2732 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
@@ -241,6 +241,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
@@ -178,6 +178,7 @@ rbd=""
smartcard=""
smartcard_nss=""
opengl=""
+tpm="no"
# parse CC options first
for opt do
@@ -743,6 +744,8 @@ for opt do
;;
--enable-smartcard-nss) smartcard_nss="yes"
;;
+ --enable-tpm) tpm="yes"
+ ;;
*) echo "ERROR: unknown option $opt"; show_help="yes"
;;
esac
@@ -1018,6 +1021,7 @@ echo " --disable-smartcard disable
echo " --enable-smartcard enable smartcard support"
echo " --disable-smartcard-nss disable smartcard nss support"
echo " --enable-smartcard-nss enable smartcard nss support"
+echo " --enable-tpm enables an emulated TPM"
echo ""
echo "NOTE: The object files are built at the place where configure is launched"
exit 1
@@ -2618,6 +2622,7 @@ echo "rbd support $rbd"
echo "xfsctl support $xfs"
echo "nss used $smartcard_nss"
echo "OpenGL support $opengl"
+echo "TPM support $tpm"
if test $sdl_too_old = "yes"; then
echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -3429,6 +3434,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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 05/13] Add a debug register
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (3 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 04/13] Add tpm_tis driver to build process Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 06/13] Add a TPM backend skeleton implementation Stefan Berger
` (7 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_tis_debugreg.diff --]
[-- Type: text/plain, Size: 3269 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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 06/13] Add a TPM backend skeleton implementation
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (4 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 05/13] Add a debug register Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 07/13] Implementation of the libtpms-based backend Stefan Berger
` (6 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_be_skeleton.diff --]
[-- Type: text/plain, Size: 15209 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 | 450 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
tpm.c | 3
tpm.h | 1
5 files changed, 460 insertions(+)
Index: qemu-git/hw/tpm_builtin.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_builtin.c
@@ -0,0 +1,450 @@
+/*
+ * 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 = false;
+static bool tpm_initialized = false;
+static bool had_fatal_error = false;
+static bool had_startup_error = false;
+static bool thread_running = false;
+
+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 = qemu_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 = qemu_malloc(sizeof(TPMBackend));
+ if (!driver) {
+ fprintf(stderr, "Could not allocate memory.\n");
+ return NULL;
+ }
+ driver->id = qemu_strdup(id);
+ if (model)
+ driver->model = qemu_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:
+ qemu_free(driver->id);
+ qemu_free(driver->model);
+ qemu_free(driver);
+ return NULL;
+}
+
+
+static void tpm_builtin_destroy(TPMBackend *driver)
+{
+ qemu_free(driver->id);
+ qemu_free(driver->model);
+ qemu_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
@@ -242,6 +242,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
@@ -3445,6 +3445,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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 07/13] Implementation of the libtpms-based backend
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (5 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 06/13] Add a TPM backend skeleton implementation Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 08/13] Introduce file lock for the block layer Stefan Berger
` (5 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_backend.diff --]
[-- Type: text/plain, Size: 22611 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 | 449 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
hw/tpm_builtin.h | 56 ++++++
3 files changed, 481 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 = { .size = 0, .buffer = NULL, };
+static TPMSizedBuffer volatile_state = { .size = 0, .buffer = NULL, };
+static TPMSizedBuffer save_state = { .size = 0, .buffer = NULL, };
+static int pipefd[2] = {-1, -1};
static bool thread_terminate = false;
static bool tpm_initialized = false;
static bool had_fatal_error = false;
static bool had_startup_error = false;
static bool thread_running = false;
+static bool need_read_volatile = false;
static ThreadParams tpm_thread_params;
@@ -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,10 @@ static void *tpm_builtin_main_loop(void
fprintf(stderr, "tpm: THREAD IS STARTING\n");
#endif
- if (res != 0) {
+ res = TPMLIB_MainInit();
+ if (res != TPM_SUCCESS) {
#if defined DEBUG_TPM || defined DEBUG_TPM_SR
- fprintf(stderr, "tpm: Error: TPM initialization failed (rc=%d)\n",
+ fprintf(stderr," tpm: Error: Call to TPMLIB_MainInit() failed (rc=%d)\n",
res);
#endif
} else {
@@ -94,6 +132,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 +165,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 +177,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 +219,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 +256,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 +268,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 +297,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 +323,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 +339,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 +347,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 +571,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 +606,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 +710,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 +721,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 = qemu_realloc(sb->buffer, wanted_size);
- if (sb->buffer != NULL) {
+ res = TPM_Realloc(&sb->buffer, wanted_size);
+ if (res == TPM_SUCCESS) {
sb->size = wanted_size;
} else {
sb->size = 0;
@@ -382,7 +776,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;
}
@@ -408,7 +803,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;
@@ -435,7 +836,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
@@ -3445,7 +3445,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;
+ qemu_free(tpmsb->buffer);
+ tpmsb->buffer = NULL;
+ }
+}
+
+static inline void set_sized_buffer(TPMSizedBuffer *tpmsb,
+ uint8_t *buffer, uint32_t size)
+{
+ clear_sized_buffer(tpmsb);
+ tpmsb->size = size;
+ tpmsb->buffer = buffer;
+}
+
+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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 08/13] Introduce file lock for the block layer
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (6 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 07/13] Implementation of the libtpms-based backend Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 09/13] Add block storage support for libtpms based TPM backend Stefan Berger
` (4 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_bdrv_lock.diff --]
[-- Type: text/plain, Size: 7158 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.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
---
block.c | 40 ++++++++++++++++++++++++++++++++++
block.h | 8 ++++++
block/raw-posix.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
block/raw-win32.c | 51 ++++++++++++++++++++++++++++++++++++++++++++
block_int.h | 4 +++
5 files changed, 165 insertions(+)
Index: qemu-git/block.c
===================================================================
--- qemu-git.orig/block.c
+++ qemu-git/block.c
@@ -469,6 +469,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);
@@ -1175,6 +1177,44 @@ 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
@@ -42,6 +42,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;
@@ -95,6 +101,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
@@ -791,6 +791,66 @@ static int64_t raw_getlength(BlockDriver
}
#endif
+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;
@@ -887,6 +947,8 @@ static BlockDriver bdrv_file = {
.bdrv_truncate = raw_truncate,
.bdrv_getlength = raw_getlength,
+ .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
@@ -137,6 +137,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
@@ -213,6 +213,56 @@ static int64_t raw_getlength(BlockDriver
return l.QuadPart;
}
+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(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;
@@ -257,6 +307,7 @@ static BlockDriver bdrv_file = {
.bdrv_write = raw_write,
.bdrv_truncate = raw_truncate,
.bdrv_getlength = raw_getlength,
+ .bdrv_lock = raw_lock,
.create_options = raw_create_options,
};
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V6 09/13] Add block storage support for libtpms based TPM backend
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (7 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 08/13] Introduce file lock for the block layer Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 10/13] Encrypt state blobs using AES CBC encryption Stefan Berger
` (3 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_backend_bs.diff --]
[-- Type: text/plain, Size: 25588 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 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 | 700 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 725 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 = false;
static bool need_read_volatile = false;
static ThreadParams tpm_thread_params;
+static BlockDriverState *bs;
/* locality of the command being executed by libtpms */
static uint8_t g_locty;
@@ -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,637 @@ 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);
+
+ if ((idx = tpm_builtin_find_bs_entry_idx(dir, type)) < 0) {
+ if ((idx = tpm_builtin_create_bs_entry(bs, dir, type, 0)) < 0) {
+ return -EIO;
+ }
+ }
+
+ memcpy(entry, &dir->entries[idx], sizeof(*entry));
+
+ return 0;
+}
+
+
+static int set_bs_entry_size_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);
+
+ if ((idx = tpm_builtin_find_bs_entry_idx(dir, type)) < 0) {
+ if ((idx = tpm_builtin_create_bs_entry(bs, dir, type, 0)) < 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;
+ }
+
+ if ((rc = tpm_builtin_get_bs_entry(bs, be, &entry)) < 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 = qemu_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;
+ }
+
+ if ((rc = set_bs_entry_size_crc(bs, be, &entry, data_len, crc)) < 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
@@ -2468,6 +2468,22 @@ if test "$trace_backend" = "dtrace"; the
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
@@ -3445,6 +3461,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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 10/13] Encrypt state blobs using AES CBC encryption
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (8 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 09/13] Add block storage support for libtpms based TPM backend Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 11/13] Experimental support for block migrating TPMs state Stefan Berger
` (2 subsequent siblings)
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_backend_enc.diff --]
[-- Type: text/plain, Size: 17186 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 necessart 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.
-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 | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
qemu-config.c | 10 ++
qemu-options.hx | 22 +++-
tpm.c | 10 ++
4 files changed, 302 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,
@@ -565,6 +644,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 = qemu_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) {
+ qemu_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)
@@ -589,7 +767,7 @@ static int tpm_builtin_load_sized_data_f
goto err_exit;
}
- tsb->buffer = qemu_malloc(entry.blobsize);
+ tsb->buffer = qemu_malloc(ALIGN(entry.blobsize, AES_BLOCK_SIZE));
if (!tsb->buffer) {
rc = -ENOMEM;
goto err_exit;
@@ -597,7 +775,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;
@@ -661,7 +840,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;
}
}
@@ -1485,11 +1665,61 @@ static const char *tpm_builtin_create_de
}
+static bool tpm_builtin_parse_as_hexkey(const char *rawkey,
+ unsigned char keyvalue[32],
+ int *keysize)
+{
+ unsigned int c = 0;
+ unsigned char nib = 0;
+
+ /* skip over leading '0x' */
+ if (!strncmp(rawkey, "0x", 2)) {
+ rawkey += 2;
+ }
+
+ while (c < 64) {
+ if (rawkey[c] == 0) {
+ break;
+ }
+
+ if (rawkey[c] >= '0' && rawkey[c] <= '9') {
+ nib |= rawkey[c] - '0';
+ } else if (rawkey[c] >= 'A' && rawkey[c] <= 'F') {
+ nib |= rawkey[c] - 'A' + 10;
+ } else if (rawkey[c] >= 'a' && rawkey[c] <= 'f') {
+ nib |= rawkey[c] - 'a' + 10;
+ } else {
+ break;
+ }
+
+ keyvalue[c/2] = nib;
+
+ nib <<= 4;
+
+ c++;
+ }
+
+ 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;
+ int keysize;
+ unsigned char keyvalue[256/8];
driver = qemu_malloc(sizeof(TPMBackend));
if (!driver) {
@@ -1515,6 +1745,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
@@ -484,6 +484,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 */ }
},
};
@@ -508,6 +513,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
@@ -244,6 +244,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 == '?') {
@@ -254,6 +255,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
@@ -1713,8 +1713,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)
@@ -1743,13 +1744,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
@@ -1757,12 +1767,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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 11/13] Experimental support for block migrating TPMs state
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (9 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 10/13] Encrypt state blobs using AES CBC encryption Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 12/13] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 13/13] Add a TPM backend null driver implementation Stefan Berger
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_blkmig.diff --]
[-- Type: text/plain, Size: 2935 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 | 22 +++++++++++-----------
2 files changed, 16 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
@@ -1546,17 +1546,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)
@@ -1577,6 +1566,17 @@ 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] 14+ messages in thread
* [Qemu-devel] [PATCH V6 12/13] Support for taking measurements when kernel etc. are passed to Qemu
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (10 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 11/13] Experimental support for block migrating TPMs state Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 13/13] Add a TPM backend null driver implementation Stefan Berger
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_paravirt.diff --]
[-- Type: text/plain, Size: 11014 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.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
Makefile.target | 2 -
configure | 23 +++++++++++
hw/fw_cfg.h | 2 +
hw/pc.c | 10 +++++
sha1.c | 19 +++++++++
sha1.h | 9 ++++
tpm.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tpm.h | 31 ++++++++++++++++
8 files changed, 203 insertions(+), 1 deletion(-)
Index: qemu-git/hw/pc.c
===================================================================
--- qemu-git.orig/hw/pc.c
+++ qemu-git/hw/pc.c
@@ -677,6 +677,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));
@@ -734,6 +737,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 {
@@ -795,9 +801,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,34 @@ 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,
+} 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
@@ -267,6 +270,88 @@ void tpm_config_parse(QemuOptsList *opts
}
}
+static TPMMsrHdr *tpm_measurements;
+
+void tpm_measure_start(void)
+{
+ if (!tpm_measurements) {
+ tpm_measurements = qemu_mallocz(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 = qemu_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 = qemu_malloc(len);
+ if (buffer) {
+ if (load_image(filename, buffer) == len) {
+ tpm_measure_buffer(buffer, len, type, pcrindex,
+ filename, strlen(filename) + 1);
+ }
+ qemu_free(buffer);
+ }
+ }
+}
+
# else /* TARGET_I386 || TARGET_X86_64 */
void tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
@@ -287,5 +372,28 @@ void do_info_tpm(Monitor *mon)
monitor_printf(mon, "TPM support: not compiled\n");
}
+
# 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
@@ -241,7 +241,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 <nss3/blapi.h>
+
+int qemu_sha1(unsigned char hash[20], const unsigned char *data, uint32_t len)
+{
+ return SHA1_HashBuf(hash, 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
@@ -2471,6 +2471,29 @@ fi
# libtpms probe
if test "$tpm" = "yes" ; then
+ if $pkg_config --atleast-version=3.12.8 nss-softokn >/dev/null 2>&1 ; then
+ tpmsupport_cflags=$($pkg_config --cflags nss-softokn 2>/dev/null)
+ tpmsupport_libs="-lfreebl -lnspr4 -lnssutil3"
+ QEMU_CFLAGS="$QEMU_CFLAGS $tpmsupport_cflags"
+ LIBS="$LIBS $tpmsupport_libs"
+ else
+ feature_not_found "nss-softokn"
+ fi
+
+ # Check for nss-softokn-freebl-devel
+ cat > $TMPC <<EOF
+#include <blapi.h>
+int main(void) {
+ unsigned char hash[20];
+ char src[1];
+ return (int)SHA1_Hash(hash, src);
+}
+EOF
+
+ if ! compile_prog "" "$tpmsupport_libs" ; then
+ feature_not_found "nss-softokn-freebl-devel"
+ fi
+
cat > $TMPC <<EOF
#include <libtpms/tpm_library.h>
int main(void) { return (int)TPMLIB_GetVersion(); }
^ permalink raw reply [flat|nested] 14+ messages in thread
* [Qemu-devel] [PATCH V6 13/13] Add a TPM backend null driver implementation
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (11 preceding siblings ...)
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 12/13] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
@ 2011-07-06 16:34 ` Stefan Berger
12 siblings, 0 replies; 14+ messages in thread
From: Stefan Berger @ 2011-07-06 16:34 UTC (permalink / raw)
To: stefanb, qemu-devel; +Cc: anbang.ruan, andreas.niederl, serge
[-- Attachment #1: qemu_tpm_be_null.diff --]
[-- Type: text/plain, Size: 12541 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.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
Makefile.target | 2
configure | 8 -
hw/tpm_null.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-options.hx | 13 +-
tpm.c | 1
tpm.h | 1
6 files changed, 339 insertions(+), 11 deletions(-)
Index: qemu-git/hw/tpm_null.c
===================================================================
--- /dev/null
+++ qemu-git/hw/tpm_null.c
@@ -0,0 +1,325 @@
+/*
+ * 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 = false;
+static bool thread_running = false;
+
+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_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 = qemu_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 = qemu_malloc(sizeof(TPMBackend));
+ if (!driver) {
+ fprintf(stderr, "Could not allocate memory.\n");
+ return NULL;
+ }
+ driver->id = qemu_strdup(id);
+ if (model)
+ driver->model = qemu_strdup(model);
+ driver->ops = &tpm_null_driver;
+
+ return driver;
+}
+
+
+static void tpm_null_destroy(TPMBackend *driver)
+{
+ qemu_free(driver->id);
+ qemu_free(driver->model);
+ qemu_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
@@ -241,7 +241,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
@@ -139,6 +139,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
@@ -1716,6 +1716,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)
@@ -1731,8 +1733,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.
@@ -1773,6 +1776,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
@@ -2501,8 +2501,6 @@ EOF
libtpms=no
if compile_prog "" "-ltpms" ; then
libtpms=yes
- else
- tpm_need_pkgs="libtpms development package"
fi
fi
@@ -3486,12 +3484,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] 14+ messages in thread
end of thread, other threads:[~2011-07-06 16:38 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-07-06 16:34 [Qemu-devel] [PATCH V6 00/13] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 01/13] Support for TPM command line options Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 02/13] Add TPM (frontend) hardware interface (TPM TIS) to Qemu Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 03/13] Add persistent state handling to TPM TIS frontend driver Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 04/13] Add tpm_tis driver to build process Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 05/13] Add a debug register Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 06/13] Add a TPM backend skeleton implementation Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 07/13] Implementation of the libtpms-based backend Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 08/13] Introduce file lock for the block layer Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 09/13] Add block storage support for libtpms based TPM backend Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 10/13] Encrypt state blobs using AES CBC encryption Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 11/13] Experimental support for block migrating TPMs state Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 12/13] Support for taking measurements when kernel etc. are passed to Qemu Stefan Berger
2011-07-06 16:34 ` [Qemu-devel] [PATCH V6 13/13] Add a TPM backend null driver implementation Stefan Berger
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).