qemu/backends/tpm/tpm_passthrough.c
<<
>>
Prefs
   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)
 405