]>
Commit | Line | Data |
---|---|---|
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/error-report.h" | |
30 | #include "qemu/sockets.h" | |
31 | #include "sysemu/tpm_backend.h" | |
32 | #include "tpm_int.h" | |
33 | #include "hw/hw.h" | |
34 | #include "hw/i386/pc.h" | |
35 | #include "sysemu/tpm_backend_int.h" | |
36 | #include "tpm_tis.h" | |
37 | #include "tpm_util.h" | |
38 | ||
39 | #define DEBUG_TPM 0 | |
40 | ||
41 | #define DPRINTF(fmt, ...) do { \ | |
42 | if (DEBUG_TPM) { \ | |
43 | fprintf(stderr, fmt, ## __VA_ARGS__); \ | |
44 | } \ | |
45 | } while (0); | |
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 | TPMVersion tpm_version; | |
76 | }; | |
77 | ||
78 | typedef struct TPMPassthruState TPMPassthruState; | |
79 | ||
80 | #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" | |
81 | ||
82 | /* functions */ | |
83 | ||
84 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb); | |
85 | ||
86 | static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len) | |
87 | { | |
88 | return send_all(fd, buf, len); | |
89 | } | |
90 | ||
91 | static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) | |
92 | { | |
93 | return recv_all(fd, buf, len, true); | |
94 | } | |
95 | ||
96 | static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf) | |
97 | { | |
98 | struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf; | |
99 | ||
100 | return be32_to_cpu(resp->len); | |
101 | } | |
102 | ||
103 | /* | |
104 | * Write an error message in the given output buffer. | |
105 | */ | |
106 | static void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len) | |
107 | { | |
108 | if (out_len >= sizeof(struct tpm_resp_hdr)) { | |
109 | struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out; | |
110 | ||
111 | resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND); | |
112 | resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr)); | |
113 | resp->errcode = cpu_to_be32(TPM_FAIL); | |
114 | } | |
115 | } | |
116 | ||
117 | static bool tpm_passthrough_is_selftest(const uint8_t *in, uint32_t in_len) | |
118 | { | |
119 | struct tpm_req_hdr *hdr = (struct tpm_req_hdr *)in; | |
120 | ||
121 | if (in_len >= sizeof(*hdr)) { | |
122 | return (be32_to_cpu(hdr->ordinal) == TPM_ORD_ContinueSelfTest); | |
123 | } | |
124 | ||
125 | return false; | |
126 | } | |
127 | ||
128 | static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, | |
129 | const uint8_t *in, uint32_t in_len, | |
130 | uint8_t *out, uint32_t out_len, | |
131 | bool *selftest_done) | |
132 | { | |
133 | int ret; | |
134 | bool is_selftest; | |
135 | const struct tpm_resp_hdr *hdr; | |
136 | ||
137 | tpm_pt->tpm_op_canceled = false; | |
138 | tpm_pt->tpm_executing = true; | |
139 | *selftest_done = false; | |
140 | ||
141 | is_selftest = tpm_passthrough_is_selftest(in, in_len); | |
142 | ||
143 | ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len); | |
144 | if (ret != in_len) { | |
145 | if (!tpm_pt->tpm_op_canceled || | |
146 | (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { | |
147 | error_report("tpm_passthrough: error while transmitting data " | |
148 | "to TPM: %s (%i)", | |
149 | strerror(errno), errno); | |
150 | } | |
151 | goto err_exit; | |
152 | } | |
153 | ||
154 | tpm_pt->tpm_executing = false; | |
155 | ||
156 | ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); | |
157 | if (ret < 0) { | |
158 | if (!tpm_pt->tpm_op_canceled || | |
159 | (tpm_pt->tpm_op_canceled && errno != ECANCELED)) { | |
160 | error_report("tpm_passthrough: error while reading data from " | |
161 | "TPM: %s (%i)", | |
162 | strerror(errno), errno); | |
163 | } | |
164 | } else if (ret < sizeof(struct tpm_resp_hdr) || | |
165 | tpm_passthrough_get_size_from_buffer(out) != ret) { | |
166 | ret = -1; | |
167 | error_report("tpm_passthrough: received invalid response " | |
168 | "packet from TPM"); | |
169 | } | |
170 | ||
171 | if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) { | |
172 | hdr = (struct tpm_resp_hdr *)out; | |
173 | *selftest_done = (be32_to_cpu(hdr->errcode) == 0); | |
174 | } | |
175 | ||
176 | err_exit: | |
177 | if (ret < 0) { | |
178 | tpm_write_fatal_error_response(out, out_len); | |
179 | } | |
180 | ||
181 | tpm_pt->tpm_executing = false; | |
182 | ||
183 | return ret; | |
184 | } | |
185 | ||
186 | static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt, | |
187 | const TPMLocality *locty_data, | |
188 | bool *selftest_done) | |
189 | { | |
190 | return tpm_passthrough_unix_tx_bufs(tpm_pt, | |
191 | locty_data->w_buffer.buffer, | |
192 | locty_data->w_offset, | |
193 | locty_data->r_buffer.buffer, | |
194 | locty_data->r_buffer.size, | |
195 | selftest_done); | |
196 | } | |
197 | ||
198 | static void tpm_passthrough_worker_thread(gpointer data, | |
199 | gpointer user_data) | |
200 | { | |
201 | TPMPassthruThreadParams *thr_parms = user_data; | |
202 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(thr_parms->tb); | |
203 | TPMBackendCmd cmd = (TPMBackendCmd)data; | |
204 | bool selftest_done = false; | |
205 | ||
206 | DPRINTF("tpm_passthrough: processing command type %d\n", cmd); | |
207 | ||
208 | switch (cmd) { | |
209 | case TPM_BACKEND_CMD_PROCESS_CMD: | |
210 | tpm_passthrough_unix_transfer(tpm_pt, | |
211 | thr_parms->tpm_state->locty_data, | |
212 | &selftest_done); | |
213 | ||
214 | thr_parms->recv_data_callback(thr_parms->tpm_state, | |
215 | thr_parms->tpm_state->locty_number, | |
216 | selftest_done); | |
217 | break; | |
218 | case TPM_BACKEND_CMD_INIT: | |
219 | case TPM_BACKEND_CMD_END: | |
220 | case TPM_BACKEND_CMD_TPM_RESET: | |
221 | /* nothing to do */ | |
222 | break; | |
223 | } | |
224 | } | |
225 | ||
226 | /* | |
227 | * Start the TPM (thread). If it had been started before, then terminate | |
228 | * and start it again. | |
229 | */ | |
230 | static int tpm_passthrough_startup_tpm(TPMBackend *tb) | |
231 | { | |
232 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
233 | ||
234 | /* terminate a running TPM */ | |
235 | tpm_backend_thread_end(&tpm_pt->tbt); | |
236 | ||
237 | tpm_backend_thread_create(&tpm_pt->tbt, | |
238 | tpm_passthrough_worker_thread, | |
239 | &tpm_pt->tpm_thread_params); | |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
244 | static void tpm_passthrough_reset(TPMBackend *tb) | |
245 | { | |
246 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
247 | ||
248 | DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n"); | |
249 | ||
250 | tpm_passthrough_cancel_cmd(tb); | |
251 | ||
252 | tpm_backend_thread_end(&tpm_pt->tbt); | |
253 | ||
254 | tpm_pt->had_startup_error = false; | |
255 | } | |
256 | ||
257 | static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, | |
258 | TPMRecvDataCB *recv_data_cb) | |
259 | { | |
260 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
261 | ||
262 | tpm_pt->tpm_thread_params.tpm_state = s; | |
263 | tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb; | |
264 | tpm_pt->tpm_thread_params.tb = tb; | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) | |
270 | { | |
271 | return false; | |
272 | } | |
273 | ||
274 | static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, | |
275 | uint8_t locty) | |
276 | { | |
277 | /* only a TPM 2.0 will support this */ | |
278 | return 0; | |
279 | } | |
280 | ||
281 | static bool tpm_passthrough_get_startup_error(TPMBackend *tb) | |
282 | { | |
283 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
284 | ||
285 | return tpm_pt->had_startup_error; | |
286 | } | |
287 | ||
288 | static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb) | |
289 | { | |
290 | size_t wanted_size = 4096; /* Linux tpm.c buffer size */ | |
291 | ||
292 | if (sb->size != wanted_size) { | |
293 | sb->buffer = g_realloc(sb->buffer, wanted_size); | |
294 | sb->size = wanted_size; | |
295 | } | |
296 | return sb->size; | |
297 | } | |
298 | ||
299 | static void tpm_passthrough_deliver_request(TPMBackend *tb) | |
300 | { | |
301 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
302 | ||
303 | tpm_backend_thread_deliver_request(&tpm_pt->tbt); | |
304 | } | |
305 | ||
306 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb) | |
307 | { | |
308 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
309 | int n; | |
310 | ||
311 | /* | |
312 | * As of Linux 3.7 the tpm_tis driver does not properly cancel | |
313 | * commands on all TPM manufacturers' TPMs. | |
314 | * Only cancel if we're busy so we don't cancel someone else's | |
315 | * command, e.g., a command executed on the host. | |
316 | */ | |
317 | if (tpm_pt->tpm_executing) { | |
318 | if (tpm_pt->cancel_fd >= 0) { | |
319 | n = write(tpm_pt->cancel_fd, "-", 1); | |
320 | if (n != 1) { | |
321 | error_report("Canceling TPM command failed: %s", | |
322 | strerror(errno)); | |
323 | } else { | |
324 | tpm_pt->tpm_op_canceled = true; | |
325 | } | |
326 | } else { | |
327 | error_report("Cannot cancel TPM command due to missing " | |
328 | "TPM sysfs cancel entry"); | |
329 | } | |
330 | } | |
331 | } | |
332 | ||
333 | static const char *tpm_passthrough_create_desc(void) | |
334 | { | |
335 | return "Passthrough TPM backend driver"; | |
336 | } | |
337 | ||
338 | static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb) | |
339 | { | |
340 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
341 | ||
342 | return tpm_pt->tpm_version; | |
343 | } | |
344 | ||
345 | /* | |
346 | * Unless path or file descriptor set has been provided by user, | |
347 | * determine the sysfs cancel file following kernel documentation | |
348 | * in Documentation/ABI/stable/sysfs-class-tpm. | |
349 | * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel | |
350 | */ | |
351 | static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) | |
352 | { | |
353 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
354 | int fd = -1; | |
355 | char *dev; | |
356 | char path[PATH_MAX]; | |
357 | ||
358 | if (tb->cancel_path) { | |
359 | fd = qemu_open(tb->cancel_path, O_WRONLY); | |
360 | if (fd < 0) { | |
361 | error_report("Could not open TPM cancel path : %s", | |
362 | strerror(errno)); | |
363 | } | |
364 | return fd; | |
365 | } | |
366 | ||
367 | dev = strrchr(tpm_pt->tpm_dev, '/'); | |
368 | if (dev) { | |
369 | dev++; | |
370 | if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", | |
371 | dev) < sizeof(path)) { | |
372 | fd = qemu_open(path, O_WRONLY); | |
373 | if (fd >= 0) { | |
374 | tb->cancel_path = g_strdup(path); | |
375 | } else { | |
376 | error_report("tpm_passthrough: Could not open TPM cancel " | |
377 | "path %s : %s", path, strerror(errno)); | |
378 | } | |
379 | } | |
380 | } else { | |
381 | error_report("tpm_passthrough: Bad TPM device path %s", | |
382 | tpm_pt->tpm_dev); | |
383 | } | |
384 | ||
385 | return fd; | |
386 | } | |
387 | ||
388 | static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) | |
389 | { | |
390 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
391 | const char *value; | |
392 | ||
393 | value = qemu_opt_get(opts, "cancel-path"); | |
394 | tb->cancel_path = g_strdup(value); | |
395 | ||
396 | value = qemu_opt_get(opts, "path"); | |
397 | if (!value) { | |
398 | value = TPM_PASSTHROUGH_DEFAULT_DEVICE; | |
399 | } | |
400 | ||
401 | tpm_pt->tpm_dev = g_strdup(value); | |
402 | ||
403 | tb->path = g_strdup(tpm_pt->tpm_dev); | |
404 | ||
405 | tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR); | |
406 | if (tpm_pt->tpm_fd < 0) { | |
407 | error_report("Cannot access TPM device using '%s': %s", | |
408 | tpm_pt->tpm_dev, strerror(errno)); | |
409 | goto err_free_parameters; | |
410 | } | |
411 | ||
412 | if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) { | |
413 | error_report("'%s' is not a TPM device.", | |
414 | tpm_pt->tpm_dev); | |
415 | goto err_close_tpmdev; | |
416 | } | |
417 | ||
418 | return 0; | |
419 | ||
420 | err_close_tpmdev: | |
421 | qemu_close(tpm_pt->tpm_fd); | |
422 | tpm_pt->tpm_fd = -1; | |
423 | ||
424 | err_free_parameters: | |
425 | g_free(tb->path); | |
426 | tb->path = NULL; | |
427 | ||
428 | g_free(tpm_pt->tpm_dev); | |
429 | tpm_pt->tpm_dev = NULL; | |
430 | ||
431 | return 1; | |
432 | } | |
433 | ||
434 | static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) | |
435 | { | |
436 | Object *obj = object_new(TYPE_TPM_PASSTHROUGH); | |
437 | TPMBackend *tb = TPM_BACKEND(obj); | |
438 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
439 | ||
440 | tb->id = g_strdup(id); | |
441 | /* let frontend set the fe_model to proper value */ | |
442 | tb->fe_model = -1; | |
443 | ||
444 | tb->ops = &tpm_passthrough_driver; | |
445 | ||
446 | if (tpm_passthrough_handle_device_opts(opts, tb)) { | |
447 | goto err_exit; | |
448 | } | |
449 | ||
450 | tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb); | |
451 | if (tpm_pt->cancel_fd < 0) { | |
452 | goto err_exit; | |
453 | } | |
454 | ||
455 | return tb; | |
456 | ||
457 | err_exit: | |
458 | g_free(tb->id); | |
459 | ||
460 | return NULL; | |
461 | } | |
462 | ||
463 | static void tpm_passthrough_destroy(TPMBackend *tb) | |
464 | { | |
465 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
466 | ||
467 | tpm_passthrough_cancel_cmd(tb); | |
468 | ||
469 | tpm_backend_thread_end(&tpm_pt->tbt); | |
470 | ||
471 | qemu_close(tpm_pt->tpm_fd); | |
472 | qemu_close(tpm_pt->cancel_fd); | |
473 | ||
474 | g_free(tb->id); | |
475 | g_free(tb->path); | |
476 | g_free(tb->cancel_path); | |
477 | g_free(tpm_pt->tpm_dev); | |
478 | } | |
479 | ||
480 | static const QemuOptDesc tpm_passthrough_cmdline_opts[] = { | |
481 | TPM_STANDARD_CMDLINE_OPTS, | |
482 | { | |
483 | .name = "cancel-path", | |
484 | .type = QEMU_OPT_STRING, | |
485 | .help = "Sysfs file entry for canceling TPM commands", | |
486 | }, | |
487 | { | |
488 | .name = "path", | |
489 | .type = QEMU_OPT_STRING, | |
490 | .help = "Path to TPM device on the host", | |
491 | }, | |
492 | { /* end of list */ }, | |
493 | }; | |
494 | ||
495 | static const TPMDriverOps tpm_passthrough_driver = { | |
496 | .type = TPM_TYPE_PASSTHROUGH, | |
497 | .opts = tpm_passthrough_cmdline_opts, | |
498 | .desc = tpm_passthrough_create_desc, | |
499 | .create = tpm_passthrough_create, | |
500 | .destroy = tpm_passthrough_destroy, | |
501 | .init = tpm_passthrough_init, | |
502 | .startup_tpm = tpm_passthrough_startup_tpm, | |
503 | .realloc_buffer = tpm_passthrough_realloc_buffer, | |
504 | .reset = tpm_passthrough_reset, | |
505 | .had_startup_error = tpm_passthrough_get_startup_error, | |
506 | .deliver_request = tpm_passthrough_deliver_request, | |
507 | .cancel_cmd = tpm_passthrough_cancel_cmd, | |
508 | .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag, | |
509 | .reset_tpm_established_flag = tpm_passthrough_reset_tpm_established_flag, | |
510 | .get_tpm_version = tpm_passthrough_get_tpm_version, | |
511 | }; | |
512 | ||
513 | static void tpm_passthrough_inst_init(Object *obj) | |
514 | { | |
515 | } | |
516 | ||
517 | static void tpm_passthrough_inst_finalize(Object *obj) | |
518 | { | |
519 | } | |
520 | ||
521 | static void tpm_passthrough_class_init(ObjectClass *klass, void *data) | |
522 | { | |
523 | TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass); | |
524 | ||
525 | tbc->ops = &tpm_passthrough_driver; | |
526 | } | |
527 | ||
528 | static const TypeInfo tpm_passthrough_info = { | |
529 | .name = TYPE_TPM_PASSTHROUGH, | |
530 | .parent = TYPE_TPM_BACKEND, | |
531 | .instance_size = sizeof(TPMPassthruState), | |
532 | .class_init = tpm_passthrough_class_init, | |
533 | .instance_init = tpm_passthrough_inst_init, | |
534 | .instance_finalize = tpm_passthrough_inst_finalize, | |
535 | }; | |
536 | ||
537 | static void tpm_passthrough_register(void) | |
538 | { | |
539 | type_register_static(&tpm_passthrough_info); | |
540 | tpm_register_driver(&tpm_passthrough_driver); | |
541 | } | |
542 | ||
543 | type_init(tpm_passthrough_register) |