cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

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)