aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/specs/tpm.txt106
-rw-r--r--hw/tpm/tpm_emulator.c323
-rw-r--r--hw/tpm/tpm_tis.c52
-rw-r--r--hw/tpm/trace-events9
-rw-r--r--tests/Makefile.include3
-rw-r--r--tests/tpm-crb-swtpm-test.c247
-rw-r--r--tests/tpm-util.c186
-rw-r--r--tests/tpm-util.h36
8 files changed, 948 insertions, 14 deletions
diff --git a/docs/specs/tpm.txt b/docs/specs/tpm.txt
index d1d7157..c230c4c 100644
--- a/docs/specs/tpm.txt
+++ b/docs/specs/tpm.txt
@@ -200,3 +200,109 @@ crw-------. 1 root root 10, 224 Jul 11 10:11 /dev/tpm0
PCR-00: 35 4E 3B CE 23 9F 38 59 ...
...
PCR-23: 00 00 00 00 00 00 00 00 ...
+
+
+=== Migration with the TPM emulator ===
+
+The TPM emulator supports the following types of virtual machine migration:
+
+- VM save / restore (migration into a file)
+- Network migration
+- Snapshotting (migration into storage like QoW2 or QED)
+
+The following command sequences can be used to test VM save / restore.
+
+
+In a 1st terminal start an instance of a swtpm using the following command:
+
+mkdir /tmp/mytpm1
+swtpm socket --tpmstate dir=/tmp/mytpm1 \
+ --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \
+ --log level=20 --tpm2
+
+In a 2nd terminal start the VM:
+
+qemu-system-x86_64 -display sdl -enable-kvm \
+ -m 1024 -boot d -bios bios-256k.bin -boot menu=on \
+ -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
+ -tpmdev emulator,id=tpm0,chardev=chrtpm \
+ -device tpm-tis,tpmdev=tpm0 \
+ -monitor stdio \
+ test.img
+
+Verify that the attached TPM is working as expected using applications inside
+the VM.
+
+To store the state of the VM use the following command in the QEMU monitor in
+the 2nd terminal:
+
+(qemu) migrate "exec:cat > testvm.bin"
+(qemu) quit
+
+At this point a file called 'testvm.bin' should exists and the swtpm and QEMU
+processes should have ended.
+
+To test 'VM restore' you have to start the swtpm with the same parameters
+as before. If previously a TPM 2 [--tpm2] was saved, --tpm2 must now be
+passed again on the command line.
+
+In the 1st terminal restart the swtpm with the same command line as before:
+
+swtpm socket --tpmstate dir=/tmp/mytpm1 \
+ --ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \
+ --log level=20 --tpm2
+
+In the 2nd terminal restore the state of the VM using the additonal
+'-incoming' option.
+
+qemu-system-x86_64 -display sdl -enable-kvm \
+ -m 1024 -boot d -bios bios-256k.bin -boot menu=on \
+ -chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock \
+ -tpmdev emulator,id=tpm0,chardev=chrtpm \
+ -device tpm-tis,tpmdev=tpm0 \
+ -incoming "exec:cat < testvm.bin" \
+ test.img
+
+
+Troubleshooting migration:
+
+There are several reasons why migration may fail. In case of problems,
+please ensure that the command lines adhere to the following rules and,
+if possible, that identical versions of QEMU and swtpm are used at all
+times.
+
+VM save and restore:
+ - QEMU command line parameters should be identical apart from the
+ '-incoming' option on VM restore
+ - swtpm command line parameters should be identical
+
+VM migration to 'localhost':
+ - QEMU command line parameters should be identical apart from the
+ '-incoming' option on the destination side
+ - swtpm command line parameters should point to two different
+ directories on the source and destination swtpm (--tpmstate dir=...)
+ (especially if different versions of libtpms were to be used on the
+ same machine).
+
+VM migration across the network:
+ - QEMU command line parameters should be identical apart from the
+ '-incoming' option on the destination side
+ - swtpm command line parameters should be identical
+
+VM Snapshotting:
+ - QEMU command line parameters should be identical
+ - swtpm command line parameters should be identical
+
+
+Besides that, migration failure reasons on the swtpm level may include
+the following:
+
+ - the versions of the swtpm on the source and destination sides are
+ incompatible
+ - downgrading of TPM state may not be supported
+ - the source and destination libtpms were compiled with different
+ compile-time options and the destination side refuses to accept the
+ state
+ - different migration keys are used on the source and destination side
+ and the destination side cannot decrypt the migrated state
+ (swtpm ... --migration-key ... )
diff --git a/hw/tpm/tpm_emulator.c b/hw/tpm/tpm_emulator.c
index 6418ef0..10bc20d 100644
--- a/hw/tpm/tpm_emulator.c
+++ b/hw/tpm/tpm_emulator.c
@@ -4,7 +4,7 @@
* Copyright (c) 2017 Intel Corporation
* Author: Amarnath Valluri <amarnath.valluri@intel.com>
*
- * Copyright (c) 2010 - 2013 IBM Corporation
+ * Copyright (c) 2010 - 2013, 2018 IBM Corporation
* Authors:
* Stefan Berger <stefanb@us.ibm.com>
*
@@ -49,6 +49,19 @@
#define TPM_EMULATOR_IMPLEMENTS_ALL_CAPS(S, cap) (((S)->caps & (cap)) == (cap))
/* data structures */
+
+/* blobs from the TPM; part of VM state when migrating */
+typedef struct TPMBlobBuffers {
+ uint32_t permanent_flags;
+ TPMSizedBuffer permanent;
+
+ uint32_t volatil_flags;
+ TPMSizedBuffer volatil;
+
+ uint32_t savestate_flags;
+ TPMSizedBuffer savestate;
+} TPMBlobBuffers;
+
typedef struct TPMEmulator {
TPMBackend parent;
@@ -64,6 +77,8 @@ typedef struct TPMEmulator {
unsigned int established_flag:1;
unsigned int established_flag_cached:1;
+
+ TPMBlobBuffers state_blobs;
} TPMEmulator;
@@ -293,7 +308,8 @@ static int tpm_emulator_set_buffer_size(TPMBackend *tb,
return 0;
}
-static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
+static int tpm_emulator_startup_tpm_resume(TPMBackend *tb, size_t buffersize,
+ bool is_resume)
{
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
ptm_init init = {
@@ -301,12 +317,17 @@ static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
};
ptm_res res;
+ trace_tpm_emulator_startup_tpm_resume(is_resume, buffersize);
+
if (buffersize != 0 &&
tpm_emulator_set_buffer_size(tb, buffersize, NULL) < 0) {
goto err_exit;
}
- trace_tpm_emulator_startup_tpm();
+ if (is_resume) {
+ init.u.req.init_flags |= cpu_to_be32(PTM_INIT_FLAG_DELETE_VOLATILE);
+ }
+
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_INIT, &init, sizeof(init),
sizeof(init)) < 0) {
error_report("tpm-emulator: could not send INIT: %s",
@@ -325,6 +346,11 @@ err_exit:
return -1;
}
+static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
+{
+ return tpm_emulator_startup_tpm_resume(tb, buffersize, false);
+}
+
static bool tpm_emulator_get_tpm_established_flag(TPMBackend *tb)
{
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
@@ -423,16 +449,21 @@ static size_t tpm_emulator_get_buffer_size(TPMBackend *tb)
static int tpm_emulator_block_migration(TPMEmulator *tpm_emu)
{
Error *err = NULL;
+ ptm_cap caps = PTM_CAP_GET_STATEBLOB | PTM_CAP_SET_STATEBLOB |
+ PTM_CAP_STOP;
- error_setg(&tpm_emu->migration_blocker,
- "Migration disabled: TPM emulator not yet migratable");
- migrate_add_blocker(tpm_emu->migration_blocker, &err);
- if (err) {
- error_report_err(err);
- error_free(tpm_emu->migration_blocker);
- tpm_emu->migration_blocker = NULL;
+ if (!TPM_EMULATOR_IMPLEMENTS_ALL_CAPS(tpm_emu, caps)) {
+ error_setg(&tpm_emu->migration_blocker,
+ "Migration disabled: TPM emulator does not support "
+ "migration");
+ migrate_add_blocker(tpm_emu->migration_blocker, &err);
+ if (err) {
+ error_report_err(err);
+ error_free(tpm_emu->migration_blocker);
+ tpm_emu->migration_blocker = NULL;
- return -1;
+ return -1;
+ }
}
return 0;
@@ -570,6 +601,267 @@ static const QemuOptDesc tpm_emulator_cmdline_opts[] = {
{ /* end of list */ },
};
+/*
+ * Transfer a TPM state blob from the TPM into a provided buffer.
+ *
+ * @tpm_emu: TPMEmulator
+ * @type: the type of blob to transfer
+ * @tsb: the TPMSizeBuffer to fill with the blob
+ * @flags: the flags to return to the caller
+ */
+static int tpm_emulator_get_state_blob(TPMEmulator *tpm_emu,
+ uint8_t type,
+ TPMSizedBuffer *tsb,
+ uint32_t *flags)
+{
+ ptm_getstate pgs;
+ ptm_res res;
+ ssize_t n;
+ uint32_t totlength, length;
+
+ tpm_sized_buffer_reset(tsb);
+
+ pgs.u.req.state_flags = cpu_to_be32(PTM_STATE_FLAG_DECRYPTED);
+ pgs.u.req.type = cpu_to_be32(type);
+ pgs.u.req.offset = 0;
+
+ if (tpm_emulator_ctrlcmd(tpm_emu, CMD_GET_STATEBLOB,
+ &pgs, sizeof(pgs.u.req),
+ offsetof(ptm_getstate, u.resp.data)) < 0) {
+ error_report("tpm-emulator: could not get state blob type %d : %s",
+ type, strerror(errno));
+ return -1;
+ }
+
+ res = be32_to_cpu(pgs.u.resp.tpm_result);
+ if (res != 0 && (res & 0x800) == 0) {
+ error_report("tpm-emulator: Getting the stateblob (type %d) failed "
+ "with a TPM error 0x%x", type, res);
+ return -1;
+ }
+
+ totlength = be32_to_cpu(pgs.u.resp.totlength);
+ length = be32_to_cpu(pgs.u.resp.length);
+ if (totlength != length) {
+ error_report("tpm-emulator: Expecting to read %u bytes "
+ "but would get %u", totlength, length);
+ return -1;
+ }
+
+ *flags = be32_to_cpu(pgs.u.resp.state_flags);
+
+ if (totlength > 0) {
+ tsb->buffer = g_try_malloc(totlength);
+ if (!tsb->buffer) {
+ error_report("tpm-emulator: Out of memory allocating %u bytes",
+ totlength);
+ return -1;
+ }
+
+ n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr, tsb->buffer, totlength);
+ if (n != totlength) {
+ error_report("tpm-emulator: Could not read stateblob (type %d); "
+ "expected %u bytes, got %zd",
+ type, totlength, n);
+ return -1;
+ }
+ }
+ tsb->size = totlength;
+
+ trace_tpm_emulator_get_state_blob(type, tsb->size, *flags);
+
+ return 0;
+}
+
+static int tpm_emulator_get_state_blobs(TPMEmulator *tpm_emu)
+{
+ TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
+
+ if (tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
+ &state_blobs->permanent,
+ &state_blobs->permanent_flags) < 0 ||
+ tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
+ &state_blobs->volatil,
+ &state_blobs->volatil_flags) < 0 ||
+ tpm_emulator_get_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
+ &state_blobs->savestate,
+ &state_blobs->savestate_flags) < 0) {
+ goto err_exit;
+ }
+
+ return 0;
+
+ err_exit:
+ tpm_sized_buffer_reset(&state_blobs->volatil);
+ tpm_sized_buffer_reset(&state_blobs->permanent);
+ tpm_sized_buffer_reset(&state_blobs->savestate);
+
+ return -1;
+}
+
+/*
+ * Transfer a TPM state blob to the TPM emulator.
+ *
+ * @tpm_emu: TPMEmulator
+ * @type: the type of TPM state blob to transfer
+ * @tsb: TPMSizedBuffer containing the TPM state blob
+ * @flags: Flags describing the (encryption) state of the TPM state blob
+ */
+static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
+ uint32_t type,
+ TPMSizedBuffer *tsb,
+ uint32_t flags)
+{
+ ssize_t n;
+ ptm_setstate pss;
+ ptm_res tpm_result;
+
+ if (tsb->size == 0) {
+ return 0;
+ }
+
+ pss = (ptm_setstate) {
+ .u.req.state_flags = cpu_to_be32(flags),
+ .u.req.type = cpu_to_be32(type),
+ .u.req.length = cpu_to_be32(tsb->size),
+ };
+
+ /* write the header only */
+ if (tpm_emulator_ctrlcmd(tpm_emu, CMD_SET_STATEBLOB, &pss,
+ offsetof(ptm_setstate, u.req.data), 0) < 0) {
+ error_report("tpm-emulator: could not set state blob type %d : %s",
+ type, strerror(errno));
+ return -1;
+ }
+
+ /* now the body */
+ n = qemu_chr_fe_write_all(&tpm_emu->ctrl_chr, tsb->buffer, tsb->size);
+ if (n != tsb->size) {
+ error_report("tpm-emulator: Writing the stateblob (type %d) "
+ "failed; could not write %u bytes, but only %zd",
+ type, tsb->size, n);
+ return -1;
+ }
+
+ /* now get the result */
+ n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr,
+ (uint8_t *)&pss, sizeof(pss.u.resp));
+ if (n != sizeof(pss.u.resp)) {
+ error_report("tpm-emulator: Reading response from writing stateblob "
+ "(type %d) failed; expected %zu bytes, got %zd", type,
+ sizeof(pss.u.resp), n);
+ return -1;
+ }
+
+ tpm_result = be32_to_cpu(pss.u.resp.tpm_result);
+ if (tpm_result != 0) {
+ error_report("tpm-emulator: Setting the stateblob (type %d) failed "
+ "with a TPM error 0x%x", type, tpm_result);
+ return -1;
+ }
+
+ trace_tpm_emulator_set_state_blob(type, tsb->size, flags);
+
+ return 0;
+}
+
+/*
+ * Set all the TPM state blobs.
+ *
+ * Returns a negative errno code in case of error.
+ */
+static int tpm_emulator_set_state_blobs(TPMBackend *tb)
+{
+ TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
+ TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
+
+ trace_tpm_emulator_set_state_blobs();
+
+ if (tpm_emulator_stop_tpm(tb) < 0) {
+ trace_tpm_emulator_set_state_blobs_error("Could not stop TPM");
+ return -EIO;
+ }
+
+ if (tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
+ &state_blobs->permanent,
+ state_blobs->permanent_flags) < 0 ||
+ tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
+ &state_blobs->volatil,
+ state_blobs->volatil_flags) < 0 ||
+ tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
+ &state_blobs->savestate,
+ state_blobs->savestate_flags) < 0) {
+ return -EIO;
+ }
+
+ trace_tpm_emulator_set_state_blobs_done();
+
+ return 0;
+}
+
+static int tpm_emulator_pre_save(void *opaque)
+{
+ TPMBackend *tb = opaque;
+ TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
+
+ trace_tpm_emulator_pre_save();
+
+ tpm_backend_finish_sync(tb);
+
+ /* get the state blobs from the TPM */
+ return tpm_emulator_get_state_blobs(tpm_emu);
+}
+
+/*
+ * Load the TPM state blobs into the TPM.
+ *
+ * Returns negative errno codes in case of error.
+ */
+static int tpm_emulator_post_load(void *opaque, int version_id)
+{
+ TPMBackend *tb = opaque;
+ int ret;
+
+ ret = tpm_emulator_set_state_blobs(tb);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (tpm_emulator_startup_tpm_resume(tb, 0, true) < 0) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_tpm_emulator = {
+ .name = "tpm-emulator",
+ .version_id = 0,
+ .pre_save = tpm_emulator_pre_save,
+ .post_load = tpm_emulator_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(state_blobs.permanent_flags, TPMEmulator),
+ VMSTATE_UINT32(state_blobs.permanent.size, TPMEmulator),
+ VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.permanent.buffer,
+ TPMEmulator, 0, 0,
+ state_blobs.permanent.size),
+
+ VMSTATE_UINT32(state_blobs.volatil_flags, TPMEmulator),
+ VMSTATE_UINT32(state_blobs.volatil.size, TPMEmulator),
+ VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.volatil.buffer,
+ TPMEmulator, 0, 0,
+ state_blobs.volatil.size),
+
+ VMSTATE_UINT32(state_blobs.savestate_flags, TPMEmulator),
+ VMSTATE_UINT32(state_blobs.savestate.size, TPMEmulator),
+ VMSTATE_VBUFFER_ALLOC_UINT32(state_blobs.savestate.buffer,
+ TPMEmulator, 0, 0,
+ state_blobs.savestate.size),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static void tpm_emulator_inst_init(Object *obj)
{
TPMEmulator *tpm_emu = TPM_EMULATOR(obj);
@@ -579,6 +871,8 @@ static void tpm_emulator_inst_init(Object *obj)
tpm_emu->options = g_new0(TPMEmulatorOptions, 1);
tpm_emu->cur_locty_number = ~0;
qemu_mutex_init(&tpm_emu->mutex);
+
+ vmstate_register(NULL, -1, &vmstate_tpm_emulator, obj);
}
/*
@@ -600,6 +894,7 @@ static void tpm_emulator_shutdown(TPMEmulator *tpm_emu)
static void tpm_emulator_inst_finalize(Object *obj)
{
TPMEmulator *tpm_emu = TPM_EMULATOR(obj);
+ TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
tpm_emulator_shutdown(tpm_emu);
@@ -614,7 +909,13 @@ static void tpm_emulator_inst_finalize(Object *obj)
error_free(tpm_emu->migration_blocker);
}
+ tpm_sized_buffer_reset(&state_blobs->volatil);
+ tpm_sized_buffer_reset(&state_blobs->permanent);
+ tpm_sized_buffer_reset(&state_blobs->savestate);
+
qemu_mutex_destroy(&tpm_emu->mutex);
+
+ vmstate_unregister(NULL, &vmstate_tpm_emulator, obj);
}
static void tpm_emulator_class_init(ObjectClass *klass, void *data)
diff --git a/hw/tpm/tpm_tis.c b/hw/tpm/tpm_tis.c
index 2ac7e74..12f5c9a 100644
--- a/hw/tpm/tpm_tis.c
+++ b/hw/tpm/tpm_tis.c
@@ -894,9 +894,57 @@ static void tpm_tis_reset(DeviceState *dev)
tpm_backend_startup_tpm(s->be_driver, s->be_buffer_size);
}
+/* persistent state handling */
+
+static int tpm_tis_pre_save(void *opaque)
+{
+ TPMState *s = opaque;
+ uint8_t locty = s->active_locty;
+
+ trace_tpm_tis_pre_save(locty, s->rw_offset);
+
+ if (DEBUG_TIS) {
+ tpm_tis_dump_state(opaque, 0);
+ }
+
+ /*
+ * Synchronize with backend completion.
+ */
+ tpm_backend_finish_sync(s->be_driver);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_locty = {
+ .name = "tpm-tis/locty",
+ .version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(state, TPMLocality),
+ VMSTATE_UINT32(inte, TPMLocality),
+ VMSTATE_UINT32(ints, TPMLocality),
+ VMSTATE_UINT8(access, TPMLocality),
+ VMSTATE_UINT32(sts, TPMLocality),
+ VMSTATE_UINT32(iface_id, TPMLocality),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
static const VMStateDescription vmstate_tpm_tis = {
- .name = "tpm",
- .unmigratable = 1,
+ .name = "tpm-tis",
+ .version_id = 0,
+ .pre_save = tpm_tis_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(buffer, TPMState),
+ VMSTATE_UINT16(rw_offset, TPMState),
+ VMSTATE_UINT8(active_locty, TPMState),
+ VMSTATE_UINT8(aborting_locty, TPMState),
+ VMSTATE_UINT8(next_locty, TPMState),
+
+ VMSTATE_STRUCT_ARRAY(loc, TPMState, TPM_TIS_NUM_LOCALITIES, 0,
+ vmstate_locty, TPMLocality),
+
+ VMSTATE_END_OF_LIST()
+ }
};
static Property tpm_tis_properties[] = {
diff --git a/hw/tpm/trace-events b/hw/tpm/trace-events
index 9a65384..25bee0c 100644
--- a/hw/tpm/trace-events
+++ b/hw/tpm/trace-events
@@ -20,13 +20,19 @@ tpm_emulator_set_locality(uint8_t locty) "setting locality to %d"
tpm_emulator_handle_request(void) "processing TPM command"
tpm_emulator_probe_caps(uint64_t caps) "capabilities: 0x%"PRIx64
tpm_emulator_set_buffer_size(uint32_t buffersize, uint32_t minsize, uint32_t maxsize) "buffer size: %u, min: %u, max: %u"
-tpm_emulator_startup_tpm(void) "startup"
+tpm_emulator_startup_tpm_resume(bool is_resume, size_t buffersize) "is_resume: %d, buffer size: %zu"
tpm_emulator_get_tpm_established_flag(uint8_t flag) "got established flag: %d"
tpm_emulator_cancel_cmd_not_supt(void) "Backend does not support CANCEL_TPM_CMD"
tpm_emulator_handle_device_opts_tpm12(void) "TPM Version 1.2"
tpm_emulator_handle_device_opts_tpm2(void) "TPM Version 2"
tpm_emulator_handle_device_opts_unspec(void) "TPM Version Unspecified"
tpm_emulator_handle_device_opts_startup_error(void) "Startup error"
+tpm_emulator_get_state_blob(uint8_t type, uint32_t size, uint32_t flags) "got state blob type %d, %u bytes, flags 0x%08x"
+tpm_emulator_set_state_blob(uint8_t type, uint32_t size, uint32_t flags) "set state blob type %d, %u bytes, flags 0x%08x"
+tpm_emulator_set_state_blobs(void) "setting state blobs"
+tpm_emulator_set_state_blobs_error(const char *msg) "error while setting state blobs: %s"
+tpm_emulator_set_state_blobs_done(void) "Done setting state blobs"
+tpm_emulator_pre_save(void) ""
tpm_emulator_inst_init(void) ""
# hw/tpm/tpm_tis.c
@@ -44,3 +50,4 @@ tpm_tis_mmio_write_locty_seized(uint8_t locty, uint8_t active) "Locality %d seiz
tpm_tis_mmio_write_init_abort(void) "Initiating abort"
tpm_tis_mmio_write_lowering_irq(void) "Lowering IRQ"
tpm_tis_mmio_write_data2send(uint32_t value, unsigned size) "Data to send to TPM: 0x%08x (size=%d)"
+tpm_tis_pre_save(uint8_t locty, uint32_t rw_offset) "locty: %d, rw_offset = %u"
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 3b9a5e3..b499ba1 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -297,6 +297,7 @@ check-qtest-i386-$(CONFIG_VHOST_USER_NET_TEST_i386) += tests/vhost-user-test$(EX
ifeq ($(CONFIG_VHOST_USER_NET_TEST_i386),)
check-qtest-x86_64-$(CONFIG_VHOST_USER_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF)
endif
+check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-swtpm-test$(EXESUF)
check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-test$(EXESUF)
check-qtest-i386-$(CONFIG_TPM) += tests/tpm-tis-test$(EXESUF)
check-qtest-i386-$(CONFIG_SLIRP) += tests/test-netfilter$(EXESUF)
@@ -721,6 +722,8 @@ tests/test-util-sockets$(EXESUF): tests/test-util-sockets.o \
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
tests/io-channel-helpers.o tests/socket-helpers.o $(test-io-obj-y)
+tests/tpm-crb-swtpm-test$(EXESUF): tests/tpm-crb-swtpm-test.o tests/tpm-emu.o \
+ tests/tpm-util.o $(test-io-obj-y)
tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o tests/tpm-emu.o $(test-io-obj-y)
tests/tpm-tis-test$(EXESUF): tests/tpm-tis-test.o tests/tpm-emu.o $(test-io-obj-y)
tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
diff --git a/tests/tpm-crb-swtpm-test.c b/tests/tpm-crb-swtpm-test.c
new file mode 100644
index 0000000..c2bde0c
--- /dev/null
+++ b/tests/tpm-crb-swtpm-test.c
@@ -0,0 +1,247 @@
+/*
+ * QTest testcase for TPM CRB talking to external swtpm and swtpm migration
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * with parts borrowed from migration-test.c that is:
+ * Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/acpi/tpm.h"
+#include "io/channel-socket.h"
+#include "libqtest.h"
+#include "tpm-util.h"
+#include "sysemu/tpm.h"
+#include "qapi/qmp/qdict.h"
+
+typedef struct TestState {
+ char *src_tpm_path;
+ char *dst_tpm_path;
+ char *uri;
+} TestState;
+
+bool got_stop;
+
+static void migrate(QTestState *who, const char *uri)
+{
+ QDict *rsp;
+ gchar *cmd;
+
+ cmd = g_strdup_printf("{ 'execute': 'migrate',"
+ "'arguments': { 'uri': '%s' } }",
+ uri);
+ rsp = qtest_qmp(who, cmd);
+ g_free(cmd);
+ g_assert(qdict_haskey(rsp, "return"));
+ qobject_unref(rsp);
+}
+
+/*
+ * Events can get in the way of responses we are actually waiting for.
+ */
+static QDict *wait_command(QTestState *who, const char *command)
+{
+ const char *event_string;
+ QDict *response;
+
+ response = qtest_qmp(who, command);
+
+ while (qdict_haskey(response, "event")) {
+ /* OK, it was an event */
+ event_string = qdict_get_str(response, "event");
+ if (!strcmp(event_string, "STOP")) {
+ got_stop = true;
+ }
+ qobject_unref(response);
+ response = qtest_qmp_receive(who);
+ }
+ return response;
+}
+
+static void wait_for_migration_complete(QTestState *who)
+{
+ while (true) {
+ QDict *rsp, *rsp_return;
+ bool completed;
+ const char *status;
+
+ rsp = wait_command(who, "{ 'execute': 'query-migrate' }");
+ rsp_return = qdict_get_qdict(rsp, "return");
+ status = qdict_get_str(rsp_return, "status");
+ completed = strcmp(status, "completed") == 0;
+ g_assert_cmpstr(status, !=, "failed");
+ qobject_unref(rsp);
+ if (completed) {
+ return;
+ }
+ usleep(1000);
+ }
+}
+
+static void migration_start_qemu(QTestState **src_qemu, QTestState **dst_qemu,
+ SocketAddress *src_tpm_addr,
+ SocketAddress *dst_tpm_addr,
+ const char *miguri)
+{
+ char *src_qemu_args, *dst_qemu_args;
+
+ src_qemu_args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-crb,tpmdev=dev ",
+ src_tpm_addr->u.q_unix.path);
+
+ *src_qemu = qtest_init(src_qemu_args);
+
+ dst_qemu_args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-crb,tpmdev=dev "
+ "-incoming %s",
+ dst_tpm_addr->u.q_unix.path,
+ miguri);
+
+ *dst_qemu = qtest_init(dst_qemu_args);
+
+ free(src_qemu_args);
+ free(dst_qemu_args);
+}
+
+static void tpm_crb_swtpm_test(const void *data)
+{
+ char *args = NULL;
+ QTestState *s;
+ SocketAddress *addr = NULL;
+ gboolean succ;
+ GPid swtpm_pid;
+ GError *error = NULL;
+ const TestState *ts = data;
+
+ succ = tpm_util_swtpm_start(ts->src_tpm_path, &swtpm_pid, &addr, &error);
+ /* succ may be false if swtpm is not available */
+ if (!succ) {
+ return;
+ }
+
+ args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-crb,tpmdev=dev",
+ addr->u.q_unix.path);
+
+ s = qtest_start(args);
+ g_free(args);
+
+ tpm_util_startup(s, tpm_util_crb_transfer);
+ tpm_util_pcrextend(s, tpm_util_crb_transfer);
+
+ unsigned char tpm_pcrread_resp[] =
+ "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00"
+ "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85"
+ "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89"
+ "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde";
+ tpm_util_pcrread(s, tpm_util_crb_transfer, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ qtest_end();
+ tpm_util_swtpm_kill(swtpm_pid);
+
+ if (addr) {
+ g_unlink(addr->u.q_unix.path);
+ qapi_free_SocketAddress(addr);
+ }
+}
+
+static void tpm_crb_swtpm_migration_test(const void *data)
+{
+ const TestState *ts = data;
+ gboolean succ;
+ GPid src_tpm_pid, dst_tpm_pid;
+ SocketAddress *src_tpm_addr = NULL, *dst_tpm_addr = NULL;
+ GError *error = NULL;
+ QTestState *src_qemu, *dst_qemu;
+
+ succ = tpm_util_swtpm_start(ts->src_tpm_path, &src_tpm_pid,
+ &src_tpm_addr, &error);
+ /* succ may be false if swtpm is not available */
+ if (!succ) {
+ return;
+ }
+
+ succ = tpm_util_swtpm_start(ts->dst_tpm_path, &dst_tpm_pid,
+ &dst_tpm_addr, &error);
+ /* succ may be false if swtpm is not available */
+ if (!succ) {
+ goto err_src_tpm_kill;
+ }
+
+ migration_start_qemu(&src_qemu, &dst_qemu, src_tpm_addr, dst_tpm_addr,
+ ts->uri);
+
+ tpm_util_startup(src_qemu, tpm_util_crb_transfer);
+ tpm_util_pcrextend(src_qemu, tpm_util_crb_transfer);
+
+ unsigned char tpm_pcrread_resp[] =
+ "\x80\x01\x00\x00\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00"
+ "\x00\x01\x00\x0b\x03\x00\x04\x00\x00\x00\x00\x01\x00\x20\xf6\x85"
+ "\x98\xe5\x86\x8d\xe6\x8b\x97\x29\x99\x60\xf2\x71\x7d\x17\x67\x89"
+ "\xa4\x2f\x9a\xae\xa8\xc7\xb7\xaa\x79\xa8\x62\x56\xc1\xde";
+ tpm_util_pcrread(src_qemu, tpm_util_crb_transfer, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ migrate(src_qemu, ts->uri);
+ wait_for_migration_complete(src_qemu);
+
+ tpm_util_pcrread(dst_qemu, tpm_util_crb_transfer, tpm_pcrread_resp,
+ sizeof(tpm_pcrread_resp));
+
+ qtest_quit(dst_qemu);
+ qtest_quit(src_qemu);
+
+ tpm_util_swtpm_kill(dst_tpm_pid);
+ if (dst_tpm_addr) {
+ g_unlink(dst_tpm_addr->u.q_unix.path);
+ qapi_free_SocketAddress(dst_tpm_addr);
+ }
+
+err_src_tpm_kill:
+ tpm_util_swtpm_kill(src_tpm_pid);
+ if (src_tpm_addr) {
+ g_unlink(src_tpm_addr->u.q_unix.path);
+ qapi_free_SocketAddress(src_tpm_addr);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ TestState ts = { 0 };
+
+ ts.src_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL);
+ ts.dst_tpm_path = g_dir_make_tmp("qemu-tpm-crb-swtpm-test.XXXXXX", NULL);
+ ts.uri = g_strdup_printf("unix:%s/migsocket", ts.src_tpm_path);
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_data_func("/tpm/crb-swtpm/test", &ts, tpm_crb_swtpm_test);
+ qtest_add_data_func("/tpm/crb-swtpm-migration/test", &ts,
+ tpm_crb_swtpm_migration_test);
+ ret = g_test_run();
+
+ g_rmdir(ts.dst_tpm_path);
+ g_free(ts.dst_tpm_path);
+ g_rmdir(ts.src_tpm_path);
+ g_free(ts.src_tpm_path);
+ g_free(ts.uri);
+
+ return ret;
+}
diff --git a/tests/tpm-util.c b/tests/tpm-util.c
new file mode 100644
index 0000000..c9b3947
--- /dev/null
+++ b/tests/tpm-util.c
@@ -0,0 +1,186 @@
+/*
+ * QTest TPM utilities
+ *
+ * Copyright (c) 2018 IBM Corporation
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/acpi/tpm.h"
+#include "libqtest.h"
+#include "tpm-util.h"
+
+void tpm_util_crb_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size)
+{
+ uint64_t caddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+ uint64_t raddr = qtest_readq(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+
+ qtest_writeb(s, TPM_CRB_ADDR_BASE + A_CRB_LOC_CTRL, 1);
+
+ qtest_memwrite(s, caddr, req, req_size);
+
+ uint32_t sts, start = 1;
+ uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ qtest_writel(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
+ while (true) {
+ start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ if ((start & 1) == 0) {
+ break;
+ }
+ if (g_get_monotonic_time() >= end_time) {
+ break;
+ }
+ };
+ start = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ g_assert_cmpint(start & 1, ==, 0);
+ sts = qtest_readl(s, TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(sts & 1, ==, 0);
+
+ qtest_memread(s, raddr, rsp, rsp_size);
+}
+
+void tpm_util_startup(QTestState *s, tx_func *tx)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_startup[] =
+ "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00";
+ unsigned char tpm_startup_resp[] =
+ "\x80\x01\x00\x00\x00\x0a\x00\x00\x00\x00";
+
+ tx(s, tpm_startup, sizeof(tpm_startup), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, sizeof(tpm_startup_resp),
+ tpm_startup_resp, sizeof(tpm_startup_resp));
+}
+
+void tpm_util_pcrextend(QTestState *s, tx_func *tx)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_pcrextend[] =
+ "\x80\x02\x00\x00\x00\x41\x00\x00\x01\x82\x00\x00\x00\x0a\x00\x00"
+ "\x00\x09\x40\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00"
+ "\x0b\x74\x65\x73\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00";
+
+ unsigned char tpm_pcrextend_resp[] =
+ "\x80\x02\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x01\x00\x00";
+
+ tx(s, tpm_pcrextend, sizeof(tpm_pcrextend), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, sizeof(tpm_pcrextend_resp),
+ tpm_pcrextend_resp, sizeof(tpm_pcrextend_resp));
+}
+
+void tpm_util_pcrread(QTestState *s, tx_func *tx,
+ const unsigned char *exp_resp, size_t exp_resp_size)
+{
+ unsigned char buffer[1024];
+ unsigned char tpm_pcrread[] =
+ "\x80\x01\x00\x00\x00\x14\x00\x00\x01\x7e\x00\x00\x00\x01\x00\x0b"
+ "\x03\x00\x04\x00";
+
+ tx(s, tpm_pcrread, sizeof(tpm_pcrread), buffer, sizeof(buffer));
+
+ g_assert_cmpmem(buffer, exp_resp_size, exp_resp, exp_resp_size);
+}
+
+static gboolean tpm_util_swtpm_has_tpm2(void)
+{
+ gint mystdout;
+ gboolean succ;
+ unsigned i;
+ char buffer[10240];
+ ssize_t n;
+ gchar *swtpm_argv[] = {
+ g_strdup("swtpm"), g_strdup("socket"), g_strdup("--help"), NULL
+ };
+
+ succ = g_spawn_async_with_pipes(NULL, swtpm_argv, NULL,
+ G_SPAWN_SEARCH_PATH, NULL, NULL, NULL,
+ NULL, &mystdout, NULL, NULL);
+ if (!succ) {
+ goto cleanup;
+ }
+
+ n = read(mystdout, buffer, sizeof(buffer) - 1);
+ if (n < 0) {
+ goto cleanup;
+ }
+ buffer[n] = 0;
+ if (!strstr(buffer, "--tpm2")) {
+ succ = false;
+ }
+
+ cleanup:
+ for (i = 0; swtpm_argv[i]; i++) {
+ g_free(swtpm_argv[i]);
+ }
+
+ return succ;
+}
+
+gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
+ SocketAddress **addr, GError **error)
+{
+ char *swtpm_argv_tpmstate = g_strdup_printf("dir=%s", path);
+ char *swtpm_argv_ctrl = g_strdup_printf("type=unixio,path=%s/sock",
+ path);
+ gchar *swtpm_argv[] = {
+ g_strdup("swtpm"), g_strdup("socket"),
+ g_strdup("--tpmstate"), swtpm_argv_tpmstate,
+ g_strdup("--ctrl"), swtpm_argv_ctrl,
+ g_strdup("--tpm2"),
+ NULL
+ };
+ gboolean succ;
+ unsigned i;
+
+ succ = tpm_util_swtpm_has_tpm2();
+ if (!succ) {
+ goto cleanup;
+ }
+
+ *addr = g_new0(SocketAddress, 1);
+ (*addr)->type = SOCKET_ADDRESS_TYPE_UNIX;
+ (*addr)->u.q_unix.path = g_build_filename(path, "sock", NULL);
+
+ succ = g_spawn_async(NULL, swtpm_argv, NULL, G_SPAWN_SEARCH_PATH,
+ NULL, NULL, pid, error);
+
+cleanup:
+ for (i = 0; swtpm_argv[i]; i++) {
+ g_free(swtpm_argv[i]);
+ }
+
+ return succ;
+}
+
+void tpm_util_swtpm_kill(GPid pid)
+{
+ int n;
+
+ if (!pid) {
+ return;
+ }
+
+ g_spawn_close_pid(pid);
+
+ n = kill(pid, 0);
+ if (n < 0) {
+ return;
+ }
+
+ kill(pid, SIGKILL);
+}
diff --git a/tests/tpm-util.h b/tests/tpm-util.h
new file mode 100644
index 0000000..d155d99
--- /dev/null
+++ b/tests/tpm-util.h
@@ -0,0 +1,36 @@
+/*
+ * QTest TPM utilities
+ *
+ * Copyright (c) 2018 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef TESTS_TPM_UTIL_H
+#define TESTS_TPM_UTIL_H
+
+#include "qemu/osdep.h"
+#include "io/channel-socket.h"
+
+typedef void (tx_func)(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+
+void tpm_util_crb_transfer(QTestState *s,
+ const unsigned char *req, size_t req_size,
+ unsigned char *rsp, size_t rsp_size);
+
+void tpm_util_startup(QTestState *s, tx_func *tx);
+void tpm_util_pcrextend(QTestState *s, tx_func *tx);
+void tpm_util_pcrread(QTestState *s, tx_func *tx,
+ const unsigned char *exp_resp, size_t exp_resp_size);
+
+gboolean tpm_util_swtpm_start(const char *path, GPid *pid,
+ SocketAddress **addr, GError **error);
+void tpm_util_swtpm_kill(GPid pid);
+
+#endif /* TESTS_TPM_UTIL_H */