tpm_passthrough.c (11799B)
1/* 2 * passthrough TPM driver 3 * 4 * Copyright (c) 2010 - 2013 IBM Corporation 5 * Authors: 6 * Stefan Berger <stefanb@us.ibm.com> 7 * 8 * Copyright (C) 2011 IAIK, Graz University of Technology 9 * Author: Andreas Niederl 10 * 11 * This library is free software; you can redistribute it and/or 12 * modify it under the terms of the GNU Lesser General Public 13 * License as published by the Free Software Foundation; either 14 * version 2.1 of the License, or (at your option) any later version. 15 * 16 * This library is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * Lesser General Public License for more details. 20 * 21 * You should have received a copy of the GNU Lesser General Public 22 * License along with this library; if not, see <http://www.gnu.org/licenses/> 23 */ 24 25#include "qemu/osdep.h" 26#include "qemu-common.h" 27#include "qemu/error-report.h" 28#include "qemu/module.h" 29#include "qemu/sockets.h" 30#include "sysemu/tpm_backend.h" 31#include "sysemu/tpm_util.h" 32#include "tpm_int.h" 33#include "qapi/clone-visitor.h" 34#include "qapi/qapi-visit-tpm.h" 35#include "trace.h" 36#include "qom/object.h" 37 38#define TYPE_TPM_PASSTHROUGH "tpm-passthrough" 39OBJECT_DECLARE_SIMPLE_TYPE(TPMPassthruState, TPM_PASSTHROUGH) 40 41/* data structures */ 42struct TPMPassthruState { 43 TPMBackend parent; 44 45 TPMPassthroughOptions *options; 46 const char *tpm_dev; 47 int tpm_fd; 48 bool tpm_executing; 49 bool tpm_op_canceled; 50 int cancel_fd; 51 52 TPMVersion tpm_version; 53 size_t tpm_buffersize; 54}; 55 56 57#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" 58 59/* functions */ 60 61static void tpm_passthrough_cancel_cmd(TPMBackend *tb); 62 63static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) 64{ 65 int ret; 66 reread: 67 ret = read(fd, buf, len); 68 if (ret < 0) { 69 if (errno != EINTR && errno != EAGAIN) { 70 return -1; 71 } 72 goto reread; 73 } 74 return ret; 75} 76 77static void tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, 78 const uint8_t *in, uint32_t in_len, 79 uint8_t *out, uint32_t out_len, 80 bool *selftest_done, Error **errp) 81{ 82 ssize_t ret; 83 bool is_selftest; 84 85 /* FIXME: protect shared variables or use other sync mechanism */ 86 tpm_pt->tpm_op_canceled = false; 87 tpm_pt->tpm_executing = true; 88 *selftest_done = false; 89 90 is_selftest = tpm_util_is_selftest(in, in_len); 91 92 ret = qemu_write_full(tpm_pt->tpm_fd, in, in_len); 93 if (ret != in_len) { 94 if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { 95 error_setg_errno(errp, errno, "tpm_passthrough: error while " 96 "transmitting data to TPM"); 97 } 98 goto err_exit; 99 } 100 101 tpm_pt->tpm_executing = false; 102 103 ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); 104 if (ret < 0) { 105 if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { 106 error_setg_errno(errp, errno, "tpm_passthrough: error while " 107 "reading data from TPM"); 108 } 109 } else if (ret < sizeof(struct tpm_resp_hdr) || 110 tpm_cmd_get_size(out) != ret) { 111 ret = -1; 112 error_setg_errno(errp, errno, "tpm_passthrough: received invalid " 113 "response packet from TPM"); 114 } 115 116 if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) { 117 *selftest_done = tpm_cmd_get_errcode(out) == 0; 118 } 119 120err_exit: 121 if (ret < 0) { 122 tpm_util_write_fatal_error_response(out, out_len); 123 } 124 125 tpm_pt->tpm_executing = false; 126} 127 128static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd, 129 Error **errp) 130{ 131 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 132 133 trace_tpm_passthrough_handle_request(cmd); 134 135 tpm_passthrough_unix_tx_bufs(tpm_pt, cmd->in, cmd->in_len, 136 cmd->out, cmd->out_len, &cmd->selftest_done, 137 errp); 138} 139 140static void tpm_passthrough_reset(TPMBackend *tb) 141{ 142 trace_tpm_passthrough_reset(); 143 144 tpm_passthrough_cancel_cmd(tb); 145} 146 147static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) 148{ 149 return false; 150} 151 152static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, 153 uint8_t locty) 154{ 155 /* only a TPM 2.0 will support this */ 156 return 0; 157} 158 159static void tpm_passthrough_cancel_cmd(TPMBackend *tb) 160{ 161 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 162 int n; 163 164 /* 165 * As of Linux 3.7 the tpm_tis driver does not properly cancel 166 * commands on all TPM manufacturers' TPMs. 167 * Only cancel if we're busy so we don't cancel someone else's 168 * command, e.g., a command executed on the host. 169 */ 170 if (tpm_pt->tpm_executing) { 171 if (tpm_pt->cancel_fd >= 0) { 172 tpm_pt->tpm_op_canceled = true; 173 n = write(tpm_pt->cancel_fd, "-", 1); 174 if (n != 1) { 175 error_report("Canceling TPM command failed: %s", 176 strerror(errno)); 177 } 178 } else { 179 error_report("Cannot cancel TPM command due to missing " 180 "TPM sysfs cancel entry"); 181 } 182 } 183} 184 185static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb) 186{ 187 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 188 189 return tpm_pt->tpm_version; 190} 191 192static size_t tpm_passthrough_get_buffer_size(TPMBackend *tb) 193{ 194 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 195 int ret; 196 197 ret = tpm_util_get_buffer_size(tpm_pt->tpm_fd, tpm_pt->tpm_version, 198 &tpm_pt->tpm_buffersize); 199 if (ret < 0) { 200 tpm_pt->tpm_buffersize = 4096; 201 } 202 return tpm_pt->tpm_buffersize; 203} 204 205/* 206 * Unless path or file descriptor set has been provided by user, 207 * determine the sysfs cancel file following kernel documentation 208 * in Documentation/ABI/stable/sysfs-class-tpm. 209 * From /dev/tpm0 create /sys/class/tpm/tpm0/device/cancel 210 * before 4.0: /sys/class/misc/tpm0/device/cancel 211 */ 212static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt) 213{ 214 int fd = -1; 215 char *dev; 216 char path[PATH_MAX]; 217 218 if (tpm_pt->options->cancel_path) { 219 fd = qemu_open_old(tpm_pt->options->cancel_path, O_WRONLY); 220 if (fd < 0) { 221 error_report("tpm_passthrough: Could not open TPM cancel path: %s", 222 strerror(errno)); 223 } 224 return fd; 225 } 226 227 dev = strrchr(tpm_pt->tpm_dev, '/'); 228 if (!dev) { 229 error_report("tpm_passthrough: Bad TPM device path %s", 230 tpm_pt->tpm_dev); 231 return -1; 232 } 233 234 dev++; 235 if (snprintf(path, sizeof(path), "/sys/class/tpm/%s/device/cancel", 236 dev) < sizeof(path)) { 237 fd = qemu_open_old(path, O_WRONLY); 238 if (fd < 0) { 239 if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", 240 dev) < sizeof(path)) { 241 fd = qemu_open_old(path, O_WRONLY); 242 } 243 } 244 } 245 246 if (fd < 0) { 247 error_report("tpm_passthrough: Could not guess TPM cancel path"); 248 } else { 249 tpm_pt->options->cancel_path = g_strdup(path); 250 } 251 252 return fd; 253} 254 255static int 256tpm_passthrough_handle_device_opts(TPMPassthruState *tpm_pt, QemuOpts *opts) 257{ 258 const char *value; 259 260 value = qemu_opt_get(opts, "cancel-path"); 261 if (value) { 262 tpm_pt->options->cancel_path = g_strdup(value); 263 tpm_pt->options->has_cancel_path = true; 264 } 265 266 value = qemu_opt_get(opts, "path"); 267 if (value) { 268 tpm_pt->options->has_path = true; 269 tpm_pt->options->path = g_strdup(value); 270 } 271 272 tpm_pt->tpm_dev = value ? value : TPM_PASSTHROUGH_DEFAULT_DEVICE; 273 tpm_pt->tpm_fd = qemu_open_old(tpm_pt->tpm_dev, O_RDWR); 274 if (tpm_pt->tpm_fd < 0) { 275 error_report("Cannot access TPM device using '%s': %s", 276 tpm_pt->tpm_dev, strerror(errno)); 277 return -1; 278 } 279 280 if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) { 281 error_report("'%s' is not a TPM device.", 282 tpm_pt->tpm_dev); 283 return -1; 284 } 285 286 tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tpm_pt); 287 if (tpm_pt->cancel_fd < 0) { 288 return -1; 289 } 290 291 return 0; 292} 293 294static TPMBackend *tpm_passthrough_create(QemuOpts *opts) 295{ 296 Object *obj = object_new(TYPE_TPM_PASSTHROUGH); 297 298 if (tpm_passthrough_handle_device_opts(TPM_PASSTHROUGH(obj), opts)) { 299 object_unref(obj); 300 return NULL; 301 } 302 303 return TPM_BACKEND(obj); 304} 305 306static int tpm_passthrough_startup_tpm(TPMBackend *tb, size_t buffersize) 307{ 308 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 309 310 if (buffersize && buffersize < tpm_pt->tpm_buffersize) { 311 error_report("Requested buffer size of %zu is smaller than host TPM's " 312 "fixed buffer size of %zu", 313 buffersize, tpm_pt->tpm_buffersize); 314 return -1; 315 } 316 317 return 0; 318} 319 320static TpmTypeOptions *tpm_passthrough_get_tpm_options(TPMBackend *tb) 321{ 322 TpmTypeOptions *options = g_new0(TpmTypeOptions, 1); 323 324 options->type = TPM_TYPE_PASSTHROUGH; 325 options->u.passthrough.data = QAPI_CLONE(TPMPassthroughOptions, 326 TPM_PASSTHROUGH(tb)->options); 327 328 return options; 329} 330 331static const QemuOptDesc tpm_passthrough_cmdline_opts[] = { 332 TPM_STANDARD_CMDLINE_OPTS, 333 { 334 .name = "cancel-path", 335 .type = QEMU_OPT_STRING, 336 .help = "Sysfs file entry for canceling TPM commands", 337 }, 338 { 339 .name = "path", 340 .type = QEMU_OPT_STRING, 341 .help = "Path to TPM device on the host", 342 }, 343 { /* end of list */ }, 344}; 345 346static void tpm_passthrough_inst_init(Object *obj) 347{ 348 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); 349 350 tpm_pt->options = g_new0(TPMPassthroughOptions, 1); 351 tpm_pt->tpm_fd = -1; 352 tpm_pt->cancel_fd = -1; 353} 354 355static void tpm_passthrough_inst_finalize(Object *obj) 356{ 357 TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); 358 359 tpm_passthrough_cancel_cmd(TPM_BACKEND(obj)); 360 361 if (tpm_pt->tpm_fd >= 0) { 362 qemu_close(tpm_pt->tpm_fd); 363 } 364 if (tpm_pt->cancel_fd >= 0) { 365 qemu_close(tpm_pt->cancel_fd); 366 } 367 qapi_free_TPMPassthroughOptions(tpm_pt->options); 368} 369 370static void tpm_passthrough_class_init(ObjectClass *klass, void *data) 371{ 372 TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass); 373 374 tbc->type = TPM_TYPE_PASSTHROUGH; 375 tbc->opts = tpm_passthrough_cmdline_opts; 376 tbc->desc = "Passthrough TPM backend driver"; 377 tbc->create = tpm_passthrough_create; 378 tbc->startup_tpm = tpm_passthrough_startup_tpm; 379 tbc->reset = tpm_passthrough_reset; 380 tbc->cancel_cmd = tpm_passthrough_cancel_cmd; 381 tbc->get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag; 382 tbc->reset_tpm_established_flag = 383 tpm_passthrough_reset_tpm_established_flag; 384 tbc->get_tpm_version = tpm_passthrough_get_tpm_version; 385 tbc->get_buffer_size = tpm_passthrough_get_buffer_size; 386 tbc->get_tpm_options = tpm_passthrough_get_tpm_options; 387 tbc->handle_request = tpm_passthrough_handle_request; 388} 389 390static const TypeInfo tpm_passthrough_info = { 391 .name = TYPE_TPM_PASSTHROUGH, 392 .parent = TYPE_TPM_BACKEND, 393 .instance_size = sizeof(TPMPassthruState), 394 .class_init = tpm_passthrough_class_init, 395 .instance_init = tpm_passthrough_inst_init, 396 .instance_finalize = tpm_passthrough_inst_finalize, 397}; 398 399static void tpm_passthrough_register(void) 400{ 401 type_register_static(&tpm_passthrough_info); 402} 403 404type_init(tpm_passthrough_register)