/* * passthrough TPM driver * * Copyright (c) 2010 - 2013 IBM Corporation * Authors: * Stefan Berger * * Copyright (C) 2011 IAIK, Graz University of Technology * Author: Andreas Niederl * * 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 */ #include #include "qemu-common.h" #include "qapi/error.h" #include "qemu/sockets.h" #include "tpm_int.h" #include "hw/hw.h" #include "hw/pc.h" #include "tpm_tis.h" #include "tpm_backend.h" /* #define DEBUG_TPM */ #ifdef DEBUG_TPM #define DPRINTF(fmt, ...) \ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) #else #define DPRINTF(fmt, ...) \ do { } while (0) #endif /* data structures */ typedef struct TPMPassthruThreadParams { TPMState *tpm_state; TPMRecvDataCB *recv_data_callback; TPMBackend *tb; } TPMPassthruThreadParams; struct TPMPassthruState { TPMBackendThread tbt; TPMPassthruThreadParams tpm_thread_params; char *tpm_dev; int tpm_fd; bool tpm_executing; bool tpm_op_canceled; int cancel_fd; bool had_startup_error; }; #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" /* functions */ static void tpm_passthrough_cancel_cmd(TPMBackend *tb); static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len) { return send_all(fd, buf, len); } static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) { return recv_all(fd, buf, len, true); } static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf) { struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf; return be32_to_cpu(resp->len); } static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_len) { int ret; tpm_pt->tpm_op_canceled = false; tpm_pt->tpm_executing = true; ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len); if (ret != in_len) { if (!tpm_pt->tpm_op_canceled || (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { error_report("tpm_passthrough: error while transmitting data " "to TPM: %s (%i)\n", strerror(errno), errno); } goto err_exit; } tpm_pt->tpm_executing = false; ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); if (ret < 0) { if (!tpm_pt->tpm_op_canceled || (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { error_report("tpm_passthrough: error while reading data from " "TPM: %s (%i)\n", strerror(errno), errno); } } else if (ret < sizeof(struct tpm_resp_hdr) || tpm_passthrough_get_size_from_buffer(out) != ret) { ret = -1; error_report("tpm_passthrough: received invalid response " "packet from TPM\n"); } err_exit: if (ret < 0) { tpm_write_fatal_error_response(out, out_len); } tpm_pt->tpm_executing = false; return ret; } static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt, const TPMLocality *locty_data) { return tpm_passthrough_unix_tx_bufs(tpm_pt, locty_data->w_buffer.buffer, locty_data->w_offset, locty_data->r_buffer.buffer, locty_data->r_buffer.size); } static void tpm_passthrough_worker_thread(gpointer data, gpointer user_data) { TPMPassthruThreadParams *thr_parms = user_data; TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt; TPMBackendCmd cmd = (TPMBackendCmd)data; DPRINTF("tpm_passthrough: processing command type %d\n", cmd); switch (cmd) { case TPM_BACKEND_CMD_PROCESS_CMD: tpm_passthrough_unix_transfer(tpm_pt, thr_parms->tpm_state->locty_data); thr_parms->recv_data_callback(thr_parms->tpm_state, thr_parms->tpm_state->locty_number); break; case TPM_BACKEND_CMD_INIT: case TPM_BACKEND_CMD_END: case TPM_BACKEND_CMD_TPM_RESET: /* nothing to do */ break; } } /* * Start the TPM (thread). If it had been started before, then terminate * and start it again. */ static int tpm_passthrough_startup_tpm(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; /* terminate a running TPM */ tpm_backend_thread_end(&tpm_pt->tbt); tpm_backend_thread_create(&tpm_pt->tbt, tpm_passthrough_worker_thread, &tb->s.tpm_pt->tpm_thread_params); return 0; } static void tpm_passthrough_reset(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n"); tpm_passthrough_cancel_cmd(tb); tpm_backend_thread_end(&tpm_pt->tbt); tpm_pt->had_startup_error = false; } static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, TPMRecvDataCB *recv_data_cb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; tpm_pt->tpm_thread_params.tpm_state = s; tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb; tpm_pt->tpm_thread_params.tb = tb; return 0; } static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) { return false; } static bool tpm_passthrough_get_startup_error(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; return tpm_pt->had_startup_error; } static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb) { size_t wanted_size = 4096; /* Linux tpm.c buffer size */ if (sb->size != wanted_size) { sb->buffer = g_realloc(sb->buffer, wanted_size); sb->size = wanted_size; } return sb->size; } static void tpm_passthrough_deliver_request(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; tpm_backend_thread_deliver_request(&tpm_pt->tbt); } static void tpm_passthrough_cancel_cmd(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; int n; /* * As of Linux 3.7 the tpm_tis driver does not properly cancel * commands on all TPM manufacturers' TPMs. * Only cancel if we're busy so we don't cancel someone else's * command, e.g., a command executed on the host. */ if (tpm_pt->tpm_executing) { if (tpm_pt->cancel_fd >= 0) { n = write(tpm_pt->cancel_fd, "-", 1); if (n != 1) { error_report("Canceling TPM command failed: %s\n", strerror(errno)); } else { tpm_pt->tpm_op_canceled = true; } } else { error_report("Cannot cancel TPM command due to missing " "TPM sysfs cancel entry"); } } } static const char *tpm_passthrough_create_desc(void) { return "Passthrough TPM backend driver"; } /* * A basic test of a TPM device. We expect a well formatted response header * (error response is fine) within one second. */ static int tpm_passthrough_test_tpmdev(int fd) { struct tpm_req_hdr req = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .len = cpu_to_be32(sizeof(req)), .ordinal = cpu_to_be32(TPM_ORD_GetTicks), }; struct tpm_resp_hdr *resp; fd_set readfds; int n; struct timeval tv = { .tv_sec = 1, .tv_usec = 0, }; unsigned char buf[1024]; n = write(fd, &req, sizeof(req)); if (n < 0) { return errno; } if (n != sizeof(req)) { return EFAULT; } FD_ZERO(&readfds); FD_SET(fd, &readfds); /* wait for a second */ n = select(fd + 1, &readfds, NULL, NULL, &tv); if (n != 1) { return errno; } n = read(fd, &buf, sizeof(buf)); if (n < sizeof(struct tpm_resp_hdr)) { return EFAULT; } resp = (struct tpm_resp_hdr *)buf; /* check the header */ if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND || be32_to_cpu(resp->len) != n) { return EBADMSG; } return 0; } /* * Check whether the given base path, e.g., /sys/class/misc/tpm0/device, * is the sysfs directory of a TPM. A TPM sysfs directory should be uniquely * recognizable by the file entries 'pcrs' and 'cancel'. * Upon success 'true' is returned and the basebath buffer has '/cancel' * appended. */ static bool tpm_passthrough_check_sysfs_cancel(char *basepath, size_t bufsz) { char path[PATH_MAX]; struct stat statbuf; snprintf(path, sizeof(path), "%s/pcrs", basepath); if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { return false; } snprintf(path, sizeof(path), "%s/cancel", basepath); if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) { return false; } strncpy(basepath, path, bufsz); return true; } /* * Unless path or file descriptor set has been provided by user, * determine the sysfs cancel file following kernel documentation * in Documentation/ABI/stable/sysfs-class-tpm. */ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) { int fd = -1; unsigned int idx; DIR *pnp_dir; char path[PATH_MAX]; struct dirent entry, *result; int len; if (tb->cancel_path) { fd = qemu_open(tb->cancel_path, O_WRONLY); if (fd < 0) { error_report("Could not open TPM cancel path : %s", strerror(errno)); } return fd; } snprintf(path, sizeof(path), "/sys/class/misc"); pnp_dir = opendir(path); if (pnp_dir != NULL) { while (readdir_r(pnp_dir, &entry, &result) == 0 && result != NULL) { /* * only allow /sys/class/misc/tpm%u type of paths */ if (sscanf(entry.d_name, "tpm%u%n", &idx, &len) < 1 || len <= strlen("tpm") || len != strlen(entry.d_name)) { continue; } snprintf(path, sizeof(path), "/sys/class/misc/%s/device", entry.d_name); if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) { continue; } fd = qemu_open(path, O_WRONLY); break; } closedir(pnp_dir); } if (fd >= 0) { tb->cancel_path = g_strdup(path); } return fd; } static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) { const char *value; value = qemu_opt_get(opts, "cancel-path"); if (value) { tb->cancel_path = g_strdup(value); } value = qemu_opt_get(opts, "path"); if (!value) { value = TPM_PASSTHROUGH_DEFAULT_DEVICE; } tb->s.tpm_pt->tpm_dev = g_strdup(value); tb->path = g_strdup(tb->s.tpm_pt->tpm_dev); tb->s.tpm_pt->tpm_fd = qemu_open(tb->s.tpm_pt->tpm_dev, O_RDWR); if (tb->s.tpm_pt->tpm_fd < 0) { error_report("Cannot access TPM device using '%s': %s\n", tb->s.tpm_pt->tpm_dev, strerror(errno)); goto err_free_parameters; } if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) { error_report("'%s' is not a TPM device.\n", tb->s.tpm_pt->tpm_dev); goto err_close_tpmdev; } return 0; err_close_tpmdev: qemu_close(tb->s.tpm_pt->tpm_fd); tb->s.tpm_pt->tpm_fd = -1; err_free_parameters: g_free(tb->path); tb->path = NULL; g_free(tb->s.tpm_pt->tpm_dev); tb->s.tpm_pt->tpm_dev = NULL; return 1; } static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) { TPMBackend *tb; tb = g_new0(TPMBackend, 1); tb->s.tpm_pt = g_new0(TPMPassthruState, 1); tb->id = g_strdup(id); /* let frontend set the fe_model to proper value */ tb->fe_model = -1; tb->ops = &tpm_passthrough_driver; if (tpm_passthrough_handle_device_opts(opts, tb)) { goto err_exit; } tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb); if (tb->s.tpm_pt->cancel_fd < 0) { goto err_exit; } return tb; err_exit: g_free(tb->id); g_free(tb->s.tpm_pt); g_free(tb); return NULL; } static void tpm_passthrough_destroy(TPMBackend *tb) { TPMPassthruState *tpm_pt = tb->s.tpm_pt; tpm_passthrough_cancel_cmd(tb); tpm_backend_thread_end(&tpm_pt->tbt); qemu_close(tpm_pt->tpm_fd); qemu_close(tb->s.tpm_pt->cancel_fd); g_free(tb->id); g_free(tb->path); g_free(tb->cancel_path); g_free(tb->s.tpm_pt->tpm_dev); g_free(tb->s.tpm_pt); g_free(tb); } const TPMDriverOps tpm_passthrough_driver = { .type = TPM_TYPE_PASSTHROUGH, .desc = tpm_passthrough_create_desc, .create = tpm_passthrough_create, .destroy = tpm_passthrough_destroy, .init = tpm_passthrough_init, .startup_tpm = tpm_passthrough_startup_tpm, .realloc_buffer = tpm_passthrough_realloc_buffer, .reset = tpm_passthrough_reset, .had_startup_error = tpm_passthrough_get_startup_error, .deliver_request = tpm_passthrough_deliver_request, .cancel_cmd = tpm_passthrough_cancel_cmd, .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag, }; static void tpm_passthrough_register(void) { tpm_register_driver(&tpm_passthrough_driver); } type_init(tpm_passthrough_register)