From patchwork Fri Jan 18 16:02:46 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Berger X-Patchwork-Id: 213670 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 74FDF2C0082 for ; Sat, 19 Jan 2013 03:24:26 +1100 (EST) Received: from localhost ([::1]:35007 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TwEjw-0006k4-E3 for incoming@patchwork.ozlabs.org; Fri, 18 Jan 2013 11:24:24 -0500 Received: from eggs.gnu.org ([208.118.235.92]:45550) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TwEVi-0006XL-M8 for qemu-devel@nongnu.org; Fri, 18 Jan 2013 11:09:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TwEVb-0008Sc-BZ for qemu-devel@nongnu.org; Fri, 18 Jan 2013 11:09:42 -0500 Received: from e7.ny.us.ibm.com ([32.97.182.137]:57273) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TwEVb-0008SK-6W for qemu-devel@nongnu.org; Fri, 18 Jan 2013 11:09:35 -0500 Received: from /spool/local by e7.ny.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Fri, 18 Jan 2013 11:09:31 -0500 Received: from d01dlp01.pok.ibm.com (9.56.250.166) by e7.ny.us.ibm.com (192.168.1.107) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Fri, 18 Jan 2013 11:08:37 -0500 Received: from d01relay05.pok.ibm.com (d01relay05.pok.ibm.com [9.56.227.237]) by d01dlp01.pok.ibm.com (Postfix) with ESMTP id 78EA738C804F for ; Fri, 18 Jan 2013 11:08:36 -0500 (EST) Received: from d03av02.boulder.ibm.com (d03av02.boulder.ibm.com [9.17.195.168]) by d01relay05.pok.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id r0IG8ZY4345326 for ; Fri, 18 Jan 2013 11:08:35 -0500 Received: from d03av02.boulder.ibm.com (loopback [127.0.0.1]) by d03av02.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id r0IG8Csd016021 for ; Fri, 18 Jan 2013 09:08:12 -0700 Received: from k-d941f-5.watson.ibm.com (k-d941f-5.watson.ibm.com [9.2.141.165]) by d03av02.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id r0IG8AhH015707 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Fri, 18 Jan 2013 09:08:11 -0700 Received: from k-d941f-5.watson.ibm.com (localhost.localdomain [127.0.0.1]) by k-d941f-5.watson.ibm.com (8.14.5/8.14.3) with ESMTP id r0IG7tpV022471; Fri, 18 Jan 2013 11:07:56 -0500 Received: (from root@localhost) by k-d941f-5.watson.ibm.com (8.14.5/8.14.5/Submit) id r0IG3DIW022360; Fri, 18 Jan 2013 11:03:13 -0500 From: Stefan Berger To: stefanb@linux.vnet.ibm.com, qemu-devel@nongnu.org, anthony@codemonkey.ws Date: Fri, 18 Jan 2013 11:02:46 -0500 Message-Id: <1358524968-22297-7-git-send-email-stefanb@linux.vnet.ibm.com> X-Mailer: git-send-email 1.7.11.7 In-Reply-To: <1358524968-22297-1-git-send-email-stefanb@linux.vnet.ibm.com> References: <1358524968-22297-1-git-send-email-stefanb@linux.vnet.ibm.com> X-Content-Scanned: Fidelis XPS MAILER x-cbid: 13011816-5806-0000-0000-00001E6D8B1E X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.4.x-2.6.x [generic] X-Received-From: 32.97.182.137 Cc: andreas.niederl@iaik.tugraz.at, mst@redhat.com Subject: [Qemu-devel] [PATCH V20 6/8] Add support for cancelling of a TPM command X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch adds support for cancelling an executing TPM command. In Linux for example a user can cancel a command through the TPM's sysfs 'cancel' entry using echo "1" > /sysfs/.../cancel This patch propagates the cancellation to the host TPM's sysfs entry. It also uses the possibility to cancel the command before QEMU VM shutdown or reboot, which helps in preventing QEMU from hanging while waiting for the completion of the command. To relieve higher layers or users from having to determine the TPM's cancel sysfs entry, the driver searches for the entry in well known locations. Signed-off-by: Stefan Berger --- hw/tpm_passthrough.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 155 insertions(+), 13 deletions(-) diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c index f9e6923..1b17c30 100644 --- a/hw/tpm_passthrough.c +++ b/hw/tpm_passthrough.c @@ -22,6 +22,8 @@ * License along with this library; if not, see */ +#include + #include "qemu-common.h" #include "qemu-error.h" #include "qemu_socket.h" @@ -57,11 +59,18 @@ struct TPMPassthruState { 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); @@ -79,25 +88,34 @@ static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf) return be32_to_cpu(resp->len); } -static int tpm_passthrough_unix_tx_bufs(int tpm_fd, +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; - ret = tpm_passthrough_unix_write(tpm_fd, in, in_len); + 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) { - error_report("tpm_passthrough: error while transmitting data " - "to TPM: %s (%i)\n", - strerror(errno), errno); + 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; } - ret = tpm_passthrough_unix_read(tpm_fd, out, out_len); + ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); if (ret < 0) { - error_report("tpm_passthrough: error while reading data from " - "TPM: %s (%i)\n", - strerror(errno), errno); + 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; @@ -110,13 +128,15 @@ err_exit: tpm_write_fatal_error_response(out, out_len); } + tpm_pt->tpm_executing = false; + return ret; } -static int tpm_passthrough_unix_transfer(int tpm_fd, +static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt, const TPMLocality *locty_data) { - return tpm_passthrough_unix_tx_bufs(tpm_fd, + return tpm_passthrough_unix_tx_bufs(tpm_pt, locty_data->w_buffer.buffer, locty_data->w_offset, locty_data->r_buffer.buffer, @@ -134,7 +154,7 @@ static void tpm_passthrough_worker_thread(gpointer data, switch (cmd) { case TPM_BACKEND_CMD_PROCESS_CMD: - tpm_passthrough_unix_transfer(tpm_pt->tpm_fd, + tpm_passthrough_unix_transfer(tpm_pt, thr_parms->tpm_state->locty_data); thr_parms->recv_data_callback(thr_parms->tpm_state, @@ -174,6 +194,8 @@ static void tpm_passthrough_reset(TPMBackend *tb) tpm_backend_thread_end(&tpm_pt->tbt); + tpm_passthrough_cancel_cmd(tb); + tpm_pt->had_startup_error = false; } @@ -221,7 +243,24 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb) static void tpm_passthrough_cancel_cmd(TPMBackend *tb) { - /* cancelling an ongoing command is known not to work with some TPMs */ + TPMPassthruState *tpm_pt = tb->s.tpm_pt; + int n; + + /* + * As of Linux 3.7 the tpm_tis driver does not properly cancel + * commands for all 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->cancel_fd >= 0 && tpm_pt->tpm_executing) { + 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; + } + } } static const char *tpm_passthrough_create_desc(void) @@ -281,6 +320,103 @@ static int tpm_passthrough_test_tpmdev(int fd) return 0; } +/* + * Check whether the given base path, e.g., /sys/devices/platfrom/tpm_tis, + * is the sysfs entry of a TPM. A TPM sysfs entry 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; +} + +static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) +{ + int fd = -1; + unsigned int i = 0; + DIR *pnp_dir; + char path[PATH_MAX]; + struct dirent entry, *result; + bool found = false; + + while (!found) { + snprintf(path, sizeof(path), "/sys/devices/pnp%u", i); + pnp_dir = opendir(path); + if (pnp_dir != NULL) { + while (readdir_r(pnp_dir, &entry, &result) == 0 && + result != NULL) { + if (!strcmp(entry.d_name, ".") || + !strcmp(entry.d_name, "..")) { + continue; + } + snprintf(path, sizeof(path), "/sys/devices/pnp%u/%s", + i, entry.d_name); + if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) { + continue; + } + + fd = open(path, O_WRONLY); + found = true; + break; + } + closedir(pnp_dir); + } else { + break; + } + i++; + } + + if (fd >= 0) { + goto exit; + } + + /* + * alternative path if ACPI for TPM was not available: + * modprobe tpm_tis force=1 + */ + snprintf(path, sizeof(path), "/sys/devices/platform/tpm_tis"); + if (tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) { + fd = open(path, O_WRONLY); + goto exit; + } + + /* + * one more thing to try: /sys/class/misc/tpm%u/device/cancel + */ + for (i = 0; i < 9; i++) { + snprintf(path, sizeof(path), "/sys/class/misc/tpm%u/device", i); + if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) { + continue; + } + fd = open(path, O_WRONLY); + break; + } + +exit: + 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; @@ -337,6 +473,8 @@ static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) goto err_exit; } + tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb); + return tb; err_exit: @@ -351,12 +489,16 @@ 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); close(tpm_pt->tpm_fd); + 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);