tpm-emu.c (7247B)
1/* 2 * Minimal TPM emulator for TPM test cases 3 * 4 * Copyright (c) 2018 Red Hat, Inc. 5 * 6 * Authors: 7 * Marc-André Lureau <marcandre.lureau@redhat.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 * See the COPYING file in the top-level directory. 11 */ 12 13#include "qemu/osdep.h" 14#include <glib/gstdio.h> 15 16#include "backends/tpm/tpm_ioctl.h" 17#include "io/channel-socket.h" 18#include "qapi/error.h" 19#include "qapi/qmp/qlist.h" 20#include "qapi/qmp/qstring.h" 21#include "tpm-emu.h" 22 23void tpm_emu_test_wait_cond(TPMTestState *s) 24{ 25 gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND; 26 27 g_mutex_lock(&s->data_mutex); 28 29 if (!s->data_cond_signal && 30 !g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) { 31 g_assert_not_reached(); 32 } 33 34 s->data_cond_signal = false; 35 36 g_mutex_unlock(&s->data_mutex); 37} 38 39static void *tpm_emu_tpm_thread(void *data) 40{ 41 TPMTestState *s = data; 42 QIOChannel *ioc = s->tpm_ioc; 43 44 s->tpm_msg = g_new(struct tpm_hdr, 1); 45 while (true) { 46 int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len); 47 48 if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) { 49 break; 50 } 51 s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag); 52 s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len); 53 g_assert_cmpint(s->tpm_msg->len, >=, minhlen); 54 55 s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len); 56 qio_channel_read(ioc, (char *)&s->tpm_msg->code, 57 s->tpm_msg->len - minhlen, &error_abort); 58 s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code); 59 60 /* reply error */ 61 switch (s->tpm_version) { 62 case TPM_VERSION_2_0: 63 s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); 64 s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr)); 65 s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE); 66 break; 67 case TPM_VERSION_1_2: 68 s->tpm_msg->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND); 69 s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr)); 70 s->tpm_msg->code = cpu_to_be32(TPM_FAIL); 71 break; 72 default: 73 g_debug("unsupport TPM version %u", s->tpm_version); 74 g_assert_not_reached(); 75 } 76 qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len), 77 &error_abort); 78 } 79 80 g_free(s->tpm_msg); 81 s->tpm_msg = NULL; 82 object_unref(OBJECT(s->tpm_ioc)); 83 return NULL; 84} 85 86void *tpm_emu_ctrl_thread(void *data) 87{ 88 TPMTestState *s = data; 89 QIOChannelSocket *lioc = qio_channel_socket_new(); 90 QIOChannel *ioc; 91 92 qio_channel_socket_listen_sync(lioc, s->addr, 1, &error_abort); 93 94 g_mutex_lock(&s->data_mutex); 95 s->data_cond_signal = true; 96 g_mutex_unlock(&s->data_mutex); 97 g_cond_signal(&s->data_cond); 98 99 qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN); 100 ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort)); 101 g_assert(ioc); 102 103 { 104 uint32_t cmd = 0; 105 struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) }; 106 int *pfd = NULL; 107 size_t nfd = 0; 108 109 qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort); 110 cmd = be32_to_cpu(cmd); 111 g_assert_cmpint(cmd, ==, CMD_SET_DATAFD); 112 g_assert_cmpint(nfd, ==, 1); 113 s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort)); 114 g_free(pfd); 115 116 cmd = 0; 117 qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort); 118 119 s->emu_tpm_thread = g_thread_new(NULL, tpm_emu_tpm_thread, s); 120 } 121 122 while (true) { 123 uint32_t cmd; 124 ssize_t ret; 125 126 ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL); 127 if (ret <= 0) { 128 break; 129 } 130 131 cmd = be32_to_cpu(cmd); 132 switch (cmd) { 133 case CMD_GET_CAPABILITY: { 134 ptm_cap cap = cpu_to_be64(0x3fff); 135 qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort); 136 break; 137 } 138 case CMD_INIT: { 139 ptm_init init; 140 qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req), 141 &error_abort); 142 init.u.resp.tpm_result = 0; 143 qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp), 144 &error_abort); 145 break; 146 } 147 case CMD_SHUTDOWN: { 148 ptm_res res = 0; 149 qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); 150 /* the tpm data thread is expected to finish now */ 151 g_thread_join(s->emu_tpm_thread); 152 break; 153 } 154 case CMD_STOP: { 155 ptm_res res = 0; 156 qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort); 157 break; 158 } 159 case CMD_SET_BUFFERSIZE: { 160 ptm_setbuffersize sbs; 161 qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req), 162 &error_abort); 163 sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096); 164 sbs.u.resp.tpm_result = 0; 165 sbs.u.resp.minsize = cpu_to_be32(128); 166 sbs.u.resp.maxsize = cpu_to_be32(4096); 167 qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp), 168 &error_abort); 169 break; 170 } 171 case CMD_SET_LOCALITY: { 172 ptm_loc loc; 173 /* Note: this time it's not u.req / u.resp... */ 174 qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort); 175 g_assert_cmpint(loc.u.req.loc, ==, 0); 176 loc.u.resp.tpm_result = 0; 177 qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort); 178 break; 179 } 180 case CMD_GET_TPMESTABLISHED: { 181 ptm_est est = { 182 .u.resp.bit = 0, 183 }; 184 qio_channel_write(ioc, (char *)&est, sizeof(est), &error_abort); 185 break; 186 } 187 default: 188 g_debug("unimplemented %u", cmd); 189 g_assert_not_reached(); 190 } 191 } 192 193 object_unref(OBJECT(ioc)); 194 object_unref(OBJECT(lioc)); 195 return NULL; 196} 197 198bool tpm_model_is_available(const char *args, const char *tpm_if) 199{ 200 QTestState *qts; 201 QDict *rsp_tpm; 202 bool ret = false; 203 204 qts = qtest_init(args); 205 if (!qts) { 206 return false; 207 } 208 209 rsp_tpm = qtest_qmp(qts, "{ 'execute': 'query-tpm'}"); 210 if (!qdict_haskey(rsp_tpm, "error")) { 211 QDict *rsp_models = qtest_qmp(qts, 212 "{ 'execute': 'query-tpm-models'}"); 213 if (qdict_haskey(rsp_models, "return")) { 214 QList *models = qdict_get_qlist(rsp_models, "return"); 215 QListEntry *e; 216 217 QLIST_FOREACH_ENTRY(models, e) { 218 QString *s = qobject_to(QString, qlist_entry_obj(e)); 219 const char *ename = qstring_get_str(s); 220 if (!strcmp(ename, tpm_if)) { 221 ret = true; 222 break; 223 } 224 } 225 } 226 qobject_unref(rsp_models); 227 } 228 qobject_unref(rsp_tpm); 229 qtest_quit(qts); 230 231 return ret; 232}