Statistics
| Branch: | Revision:

root / hw / tpm / tpm_passthrough.c @ 8e36d6ca

History | View | Annotate | Download (14.4 kB)

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 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 <dirent.h>
26

    
27
#include "qemu-common.h"
28
#include "qapi/error.h"
29
#include "qemu/sockets.h"
30
#include "sysemu/tpm_backend.h"
31
#include "tpm_int.h"
32
#include "hw/hw.h"
33
#include "hw/i386/pc.h"
34
#include "sysemu/tpm_backend_int.h"
35
#include "tpm_tis.h"
36

    
37
/* #define DEBUG_TPM */
38

    
39
#ifdef DEBUG_TPM
40
#define DPRINTF(fmt, ...) \
41
    do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
42
#else
43
#define DPRINTF(fmt, ...) \
44
    do { } while (0)
45
#endif
46

    
47
#define TYPE_TPM_PASSTHROUGH "tpm-passthrough"
48
#define TPM_PASSTHROUGH(obj) \
49
    OBJECT_CHECK(TPMPassthruState, (obj), TYPE_TPM_PASSTHROUGH)
50

    
51
static const TPMDriverOps tpm_passthrough_driver;
52

    
53
/* data structures */
54
typedef struct TPMPassthruThreadParams {
55
    TPMState *tpm_state;
56

    
57
    TPMRecvDataCB *recv_data_callback;
58
    TPMBackend *tb;
59
} TPMPassthruThreadParams;
60

    
61
struct TPMPassthruState {
62
    TPMBackend parent;
63

    
64
    TPMBackendThread tbt;
65

    
66
    TPMPassthruThreadParams tpm_thread_params;
67

    
68
    char *tpm_dev;
69
    int tpm_fd;
70
    bool tpm_executing;
71
    bool tpm_op_canceled;
72
    int cancel_fd;
73
    bool had_startup_error;
74
};
75

    
76
typedef struct TPMPassthruState TPMPassthruState;
77

    
78
#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
79

    
80
/* functions */
81

    
82
static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
83

    
84
static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
85
{
86
    return send_all(fd, buf, len);
87
}
88

    
89
static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
90
{
91
    return recv_all(fd, buf, len, true);
92
}
93

    
94
static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
95
{
96
    struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf;
97

    
98
    return be32_to_cpu(resp->len);
99
}
100

    
101
/*
102
 * Write an error message in the given output buffer.
103
 */
104
static void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
105
{
106
    if (out_len >= sizeof(struct tpm_resp_hdr)) {
107
        struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
108

    
109
        resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
110
        resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
111
        resp->errcode = cpu_to_be32(TPM_FAIL);
112
    }
113
}
114

    
115
static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
116
                                        const uint8_t *in, uint32_t in_len,
117
                                        uint8_t *out, uint32_t out_len)
118
{
119
    int ret;
120

    
121
    tpm_pt->tpm_op_canceled = false;
122
    tpm_pt->tpm_executing = true;
123

    
124
    ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
125
    if (ret != in_len) {
126
        if (!tpm_pt->tpm_op_canceled ||
127
            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
128
            error_report("tpm_passthrough: error while transmitting data "
129
                         "to TPM: %s (%i)\n",
130
                         strerror(errno), errno);
131
        }
132
        goto err_exit;
133
    }
134

    
135
    tpm_pt->tpm_executing = false;
136

    
137
    ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
138
    if (ret < 0) {
139
        if (!tpm_pt->tpm_op_canceled ||
140
            (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
141
            error_report("tpm_passthrough: error while reading data from "
142
                         "TPM: %s (%i)\n",
143
                         strerror(errno), errno);
144
        }
145
    } else if (ret < sizeof(struct tpm_resp_hdr) ||
146
               tpm_passthrough_get_size_from_buffer(out) != ret) {
147
        ret = -1;
148
        error_report("tpm_passthrough: received invalid response "
149
                     "packet from TPM\n");
150
    }
151

    
152
err_exit:
153
    if (ret < 0) {
154
        tpm_write_fatal_error_response(out, out_len);
155
    }
156

    
157
    tpm_pt->tpm_executing = false;
158

    
159
    return ret;
160
}
161

    
162
static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
163
                                         const TPMLocality *locty_data)
164
{
165
    return tpm_passthrough_unix_tx_bufs(tpm_pt,
166
                                        locty_data->w_buffer.buffer,
167
                                        locty_data->w_offset,
168
                                        locty_data->r_buffer.buffer,
169
                                        locty_data->r_buffer.size);
170
}
171

    
172
static void tpm_passthrough_worker_thread(gpointer data,
173
                                          gpointer user_data)
174
{
175
    TPMPassthruThreadParams *thr_parms = user_data;
176
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(thr_parms->tb);
177
    TPMBackendCmd cmd = (TPMBackendCmd)data;
178

    
179
    DPRINTF("tpm_passthrough: processing command type %d\n", cmd);
180

    
181
    switch (cmd) {
182
    case TPM_BACKEND_CMD_PROCESS_CMD:
183
        tpm_passthrough_unix_transfer(tpm_pt,
184
                                      thr_parms->tpm_state->locty_data);
185

    
186
        thr_parms->recv_data_callback(thr_parms->tpm_state,
187
                                      thr_parms->tpm_state->locty_number);
188
        break;
189
    case TPM_BACKEND_CMD_INIT:
190
    case TPM_BACKEND_CMD_END:
191
    case TPM_BACKEND_CMD_TPM_RESET:
192
        /* nothing to do */
193
        break;
194
    }
195
}
196

    
197
/*
198
 * Start the TPM (thread). If it had been started before, then terminate
199
 * and start it again.
200
 */
201
static int tpm_passthrough_startup_tpm(TPMBackend *tb)
202
{
203
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
204

    
205
    /* terminate a running TPM */
206
    tpm_backend_thread_end(&tpm_pt->tbt);
207

    
208
    tpm_backend_thread_create(&tpm_pt->tbt,
209
                              tpm_passthrough_worker_thread,
210
                              &tpm_pt->tpm_thread_params);
211

    
212
    return 0;
213
}
214

    
215
static void tpm_passthrough_reset(TPMBackend *tb)
216
{
217
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
218

    
219
    DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n");
220

    
221
    tpm_passthrough_cancel_cmd(tb);
222

    
223
    tpm_backend_thread_end(&tpm_pt->tbt);
224

    
225
    tpm_pt->had_startup_error = false;
226
}
227

    
228
static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
229
                                TPMRecvDataCB *recv_data_cb)
230
{
231
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
232

    
233
    tpm_pt->tpm_thread_params.tpm_state = s;
234
    tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
235
    tpm_pt->tpm_thread_params.tb = tb;
236

    
237
    return 0;
238
}
239

    
240
static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
241
{
242
    return false;
243
}
244

    
245
static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
246
{
247
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
248

    
249
    return tpm_pt->had_startup_error;
250
}
251

    
252
static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
253
{
254
    size_t wanted_size = 4096; /* Linux tpm.c buffer size */
255

    
256
    if (sb->size != wanted_size) {
257
        sb->buffer = g_realloc(sb->buffer, wanted_size);
258
        sb->size = wanted_size;
259
    }
260
    return sb->size;
261
}
262

    
263
static void tpm_passthrough_deliver_request(TPMBackend *tb)
264
{
265
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
266

    
267
    tpm_backend_thread_deliver_request(&tpm_pt->tbt);
268
}
269

    
270
static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
271
{
272
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
273
    int n;
274

    
275
    /*
276
     * As of Linux 3.7 the tpm_tis driver does not properly cancel
277
     * commands on all TPM manufacturers' TPMs.
278
     * Only cancel if we're busy so we don't cancel someone else's
279
     * command, e.g., a command executed on the host.
280
     */
281
    if (tpm_pt->tpm_executing) {
282
        if (tpm_pt->cancel_fd >= 0) {
283
            n = write(tpm_pt->cancel_fd, "-", 1);
284
            if (n != 1) {
285
                error_report("Canceling TPM command failed: %s\n",
286
                             strerror(errno));
287
            } else {
288
                tpm_pt->tpm_op_canceled = true;
289
            }
290
        } else {
291
            error_report("Cannot cancel TPM command due to missing "
292
                         "TPM sysfs cancel entry");
293
        }
294
    }
295
}
296

    
297
static const char *tpm_passthrough_create_desc(void)
298
{
299
    return "Passthrough TPM backend driver";
300
}
301

    
302
/*
303
 * A basic test of a TPM device. We expect a well formatted response header
304
 * (error response is fine) within one second.
305
 */
306
static int tpm_passthrough_test_tpmdev(int fd)
307
{
308
    struct tpm_req_hdr req = {
309
        .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
310
        .len = cpu_to_be32(sizeof(req)),
311
        .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
312
    };
313
    struct tpm_resp_hdr *resp;
314
    fd_set readfds;
315
    int n;
316
    struct timeval tv = {
317
        .tv_sec = 1,
318
        .tv_usec = 0,
319
    };
320
    unsigned char buf[1024];
321

    
322
    n = write(fd, &req, sizeof(req));
323
    if (n < 0) {
324
        return errno;
325
    }
326
    if (n != sizeof(req)) {
327
        return EFAULT;
328
    }
329

    
330
    FD_ZERO(&readfds);
331
    FD_SET(fd, &readfds);
332

    
333
    /* wait for a second */
334
    n = select(fd + 1, &readfds, NULL, NULL, &tv);
335
    if (n != 1) {
336
        return errno;
337
    }
338

    
339
    n = read(fd, &buf, sizeof(buf));
340
    if (n < sizeof(struct tpm_resp_hdr)) {
341
        return EFAULT;
342
    }
343

    
344
    resp = (struct tpm_resp_hdr *)buf;
345
    /* check the header */
346
    if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
347
        be32_to_cpu(resp->len) != n) {
348
        return EBADMSG;
349
    }
350

    
351
    return 0;
352
}
353

    
354
/*
355
 * Unless path or file descriptor set has been provided by user,
356
 * determine the sysfs cancel file following kernel documentation
357
 * in Documentation/ABI/stable/sysfs-class-tpm.
358
 * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel
359
 */
360
static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
361
{
362
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
363
    int fd = -1;
364
    char *dev;
365
    char path[PATH_MAX];
366

    
367
    if (tb->cancel_path) {
368
        fd = qemu_open(tb->cancel_path, O_WRONLY);
369
        if (fd < 0) {
370
            error_report("Could not open TPM cancel path : %s",
371
                         strerror(errno));
372
        }
373
        return fd;
374
    }
375

    
376
    dev = strrchr(tpm_pt->tpm_dev, '/');
377
    if (dev) {
378
        dev++;
379
        if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel",
380
                     dev) < sizeof(path)) {
381
            fd = qemu_open(path, O_WRONLY);
382
            if (fd >= 0) {
383
                tb->cancel_path = g_strdup(path);
384
            } else {
385
                error_report("tpm_passthrough: Could not open TPM cancel "
386
                             "path %s : %s", path, strerror(errno));
387
            }
388
        }
389
    } else {
390
       error_report("tpm_passthrough: Bad TPM device path %s",
391
                    tpm_pt->tpm_dev);
392
    }
393

    
394
    return fd;
395
}
396

    
397
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
398
{
399
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
400
    const char *value;
401

    
402
    value = qemu_opt_get(opts, "cancel-path");
403
    if (value) {
404
        tb->cancel_path = g_strdup(value);
405
    }
406

    
407
    value = qemu_opt_get(opts, "path");
408
    if (!value) {
409
        value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
410
    }
411

    
412
    tpm_pt->tpm_dev = g_strdup(value);
413

    
414
    tb->path = g_strdup(tpm_pt->tpm_dev);
415

    
416
    tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR);
417
    if (tpm_pt->tpm_fd < 0) {
418
        error_report("Cannot access TPM device using '%s': %s\n",
419
                     tpm_pt->tpm_dev, strerror(errno));
420
        goto err_free_parameters;
421
    }
422

    
423
    if (tpm_passthrough_test_tpmdev(tpm_pt->tpm_fd)) {
424
        error_report("'%s' is not a TPM device.\n",
425
                     tpm_pt->tpm_dev);
426
        goto err_close_tpmdev;
427
    }
428

    
429
    return 0;
430

    
431
 err_close_tpmdev:
432
    qemu_close(tpm_pt->tpm_fd);
433
    tpm_pt->tpm_fd = -1;
434

    
435
 err_free_parameters:
436
    g_free(tb->path);
437
    tb->path = NULL;
438

    
439
    g_free(tpm_pt->tpm_dev);
440
    tpm_pt->tpm_dev = NULL;
441

    
442
    return 1;
443
}
444

    
445
static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
446
{
447
    Object *obj = object_new(TYPE_TPM_PASSTHROUGH);
448
    TPMBackend *tb = TPM_BACKEND(obj);
449
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
450

    
451
    tb->id = g_strdup(id);
452
    /* let frontend set the fe_model to proper value */
453
    tb->fe_model = -1;
454

    
455
    tb->ops = &tpm_passthrough_driver;
456

    
457
    if (tpm_passthrough_handle_device_opts(opts, tb)) {
458
        goto err_exit;
459
    }
460

    
461
    tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
462
    if (tpm_pt->cancel_fd < 0) {
463
        goto err_exit;
464
    }
465

    
466
    return tb;
467

    
468
err_exit:
469
    g_free(tb->id);
470

    
471
    return NULL;
472
}
473

    
474
static void tpm_passthrough_destroy(TPMBackend *tb)
475
{
476
    TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
477

    
478
    tpm_passthrough_cancel_cmd(tb);
479

    
480
    tpm_backend_thread_end(&tpm_pt->tbt);
481

    
482
    qemu_close(tpm_pt->tpm_fd);
483
    qemu_close(tpm_pt->cancel_fd);
484

    
485
    g_free(tb->id);
486
    g_free(tb->path);
487
    g_free(tb->cancel_path);
488
    g_free(tpm_pt->tpm_dev);
489
}
490

    
491
static const TPMDriverOps tpm_passthrough_driver = {
492
    .type                     = TPM_TYPE_PASSTHROUGH,
493
    .desc                     = tpm_passthrough_create_desc,
494
    .create                   = tpm_passthrough_create,
495
    .destroy                  = tpm_passthrough_destroy,
496
    .init                     = tpm_passthrough_init,
497
    .startup_tpm              = tpm_passthrough_startup_tpm,
498
    .realloc_buffer           = tpm_passthrough_realloc_buffer,
499
    .reset                    = tpm_passthrough_reset,
500
    .had_startup_error        = tpm_passthrough_get_startup_error,
501
    .deliver_request          = tpm_passthrough_deliver_request,
502
    .cancel_cmd               = tpm_passthrough_cancel_cmd,
503
    .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
504
};
505

    
506
static void tpm_passthrough_inst_init(Object *obj)
507
{
508
}
509

    
510
static void tpm_passthrough_inst_finalize(Object *obj)
511
{
512
}
513

    
514
static void tpm_passthrough_class_init(ObjectClass *klass, void *data)
515
{
516
    TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass);
517

    
518
    tbc->ops = &tpm_passthrough_driver;
519
}
520

    
521
static const TypeInfo tpm_passthrough_info = {
522
    .name = TYPE_TPM_PASSTHROUGH,
523
    .parent = TYPE_TPM_BACKEND,
524
    .instance_size = sizeof(TPMPassthruState),
525
    .class_init = tpm_passthrough_class_init,
526
    .instance_init = tpm_passthrough_inst_init,
527
    .instance_finalize = tpm_passthrough_inst_finalize,
528
};
529

    
530
static void tpm_passthrough_register(void)
531
{
532
    type_register_static(&tpm_passthrough_info);
533
    tpm_register_driver(&tpm_passthrough_driver);
534
}
535

    
536
type_init(tpm_passthrough_register)