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) |