]>
Commit | Line | Data |
---|---|---|
4549a8b7 SB |
1 | /* |
2 | * passthrough TPM driver | |
3 | * | |
4 | * Copyright (c) 2010 - 2013 IBM Corporation | |
5 | * Authors: | |
6 | * Stefan Berger <[email protected]> | |
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 | ||
0430891c | 25 | #include "qemu/osdep.h" |
a8d25326 | 26 | #include "qemu-common.h" |
d49b6836 | 27 | #include "qemu/error-report.h" |
0b8fa32f | 28 | #include "qemu/module.h" |
4549a8b7 | 29 | #include "qemu/sockets.h" |
dccfcd0e | 30 | #include "sysemu/tpm_backend.h" |
0f7d2148 | 31 | #include "sysemu/tpm_util.h" |
4549a8b7 | 32 | #include "tpm_int.h" |
f59864ba | 33 | #include "qapi/clone-visitor.h" |
9af23989 | 34 | #include "qapi/qapi-visit-tpm.h" |
49d302fe | 35 | #include "trace.h" |
db1015e9 | 36 | #include "qom/object.h" |
4549a8b7 | 37 | |
8f0605cc | 38 | #define TYPE_TPM_PASSTHROUGH "tpm-passthrough" |
db1015e9 | 39 | typedef struct TPMPassthruState TPMPassthruState; |
8110fa1d EH |
40 | DECLARE_INSTANCE_CHECKER(TPMPassthruState, TPM_PASSTHROUGH, |
41 | TYPE_TPM_PASSTHROUGH) | |
4549a8b7 | 42 | |
8f0605cc | 43 | /* data structures */ |
4549a8b7 | 44 | struct TPMPassthruState { |
8f0605cc SB |
45 | TPMBackend parent; |
46 | ||
f59864ba AV |
47 | TPMPassthroughOptions *options; |
48 | const char *tpm_dev; | |
4549a8b7 | 49 | int tpm_fd; |
92dcc234 SB |
50 | bool tpm_executing; |
51 | bool tpm_op_canceled; | |
52 | int cancel_fd; | |
56a3c24f SB |
53 | |
54 | TPMVersion tpm_version; | |
abc5cda0 | 55 | size_t tpm_buffersize; |
4549a8b7 SB |
56 | }; |
57 | ||
8f0605cc | 58 | |
4549a8b7 SB |
59 | #define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0" |
60 | ||
92dcc234 SB |
61 | /* functions */ |
62 | ||
63 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb); | |
64 | ||
4549a8b7 SB |
65 | static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len) |
66 | { | |
46f296cd DB |
67 | int ret; |
68 | reread: | |
69 | ret = read(fd, buf, len); | |
70 | if (ret < 0) { | |
71 | if (errno != EINTR && errno != EAGAIN) { | |
72 | return -1; | |
73 | } | |
74 | goto reread; | |
75 | } | |
76 | return ret; | |
4549a8b7 | 77 | } |
6a8a2354 MAL |
78 | |
79 | static void tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt, | |
80 | const uint8_t *in, uint32_t in_len, | |
81 | uint8_t *out, uint32_t out_len, | |
82 | bool *selftest_done, Error **errp) | |
4549a8b7 | 83 | { |
4a3d8098 | 84 | ssize_t ret; |
fd859081 | 85 | bool is_selftest; |
4549a8b7 | 86 | |
21cb1e63 | 87 | /* FIXME: protect shared variables or use other sync mechanism */ |
92dcc234 SB |
88 | tpm_pt->tpm_op_canceled = false; |
89 | tpm_pt->tpm_executing = true; | |
fd859081 SB |
90 | *selftest_done = false; |
91 | ||
4a3d8098 | 92 | is_selftest = tpm_util_is_selftest(in, in_len); |
92dcc234 | 93 | |
54aa36d5 | 94 | ret = qemu_write_full(tpm_pt->tpm_fd, in, in_len); |
4549a8b7 | 95 | if (ret != in_len) { |
5f333d79 | 96 | if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { |
6a8a2354 MAL |
97 | error_setg_errno(errp, errno, "tpm_passthrough: error while " |
98 | "transmitting data to TPM"); | |
92dcc234 | 99 | } |
4549a8b7 SB |
100 | goto err_exit; |
101 | } | |
102 | ||
92dcc234 SB |
103 | tpm_pt->tpm_executing = false; |
104 | ||
105 | ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len); | |
4549a8b7 | 106 | if (ret < 0) { |
5f333d79 | 107 | if (!tpm_pt->tpm_op_canceled || errno != ECANCELED) { |
6a8a2354 MAL |
108 | error_setg_errno(errp, errno, "tpm_passthrough: error while " |
109 | "reading data from TPM"); | |
92dcc234 | 110 | } |
4549a8b7 | 111 | } else if (ret < sizeof(struct tpm_resp_hdr) || |
cc1b6c55 | 112 | tpm_cmd_get_size(out) != ret) { |
4549a8b7 | 113 | ret = -1; |
6a8a2354 MAL |
114 | error_setg_errno(errp, errno, "tpm_passthrough: received invalid " |
115 | "response packet from TPM"); | |
4549a8b7 SB |
116 | } |
117 | ||
fd859081 | 118 | if (is_selftest && (ret >= sizeof(struct tpm_resp_hdr))) { |
cc1b6c55 | 119 | *selftest_done = tpm_cmd_get_errcode(out) == 0; |
fd859081 SB |
120 | } |
121 | ||
4549a8b7 SB |
122 | err_exit: |
123 | if (ret < 0) { | |
4a3d8098 | 124 | tpm_util_write_fatal_error_response(out, out_len); |
4549a8b7 SB |
125 | } |
126 | ||
92dcc234 | 127 | tpm_pt->tpm_executing = false; |
4549a8b7 SB |
128 | } |
129 | ||
6a8a2354 MAL |
130 | static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd, |
131 | Error **errp) | |
4549a8b7 | 132 | { |
b19a5eea | 133 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
4549a8b7 | 134 | |
49d302fe | 135 | trace_tpm_passthrough_handle_request(cmd); |
905e78ba | 136 | |
0e43b7e6 | 137 | tpm_passthrough_unix_tx_bufs(tpm_pt, cmd->in, cmd->in_len, |
6a8a2354 MAL |
138 | cmd->out, cmd->out_len, &cmd->selftest_done, |
139 | errp); | |
4549a8b7 SB |
140 | } |
141 | ||
4549a8b7 SB |
142 | static void tpm_passthrough_reset(TPMBackend *tb) |
143 | { | |
49d302fe | 144 | trace_tpm_passthrough_reset(); |
4549a8b7 | 145 | |
92dcc234 | 146 | tpm_passthrough_cancel_cmd(tb); |
4549a8b7 SB |
147 | } |
148 | ||
149 | static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb) | |
150 | { | |
151 | return false; | |
152 | } | |
153 | ||
116694c3 SB |
154 | static int tpm_passthrough_reset_tpm_established_flag(TPMBackend *tb, |
155 | uint8_t locty) | |
156 | { | |
157 | /* only a TPM 2.0 will support this */ | |
158 | return 0; | |
159 | } | |
160 | ||
4549a8b7 SB |
161 | static void tpm_passthrough_cancel_cmd(TPMBackend *tb) |
162 | { | |
8f0605cc | 163 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
92dcc234 SB |
164 | int n; |
165 | ||
166 | /* | |
167 | * As of Linux 3.7 the tpm_tis driver does not properly cancel | |
168 | * commands on all TPM manufacturers' TPMs. | |
169 | * Only cancel if we're busy so we don't cancel someone else's | |
170 | * command, e.g., a command executed on the host. | |
171 | */ | |
172 | if (tpm_pt->tpm_executing) { | |
173 | if (tpm_pt->cancel_fd >= 0) { | |
21cb1e63 | 174 | tpm_pt->tpm_op_canceled = true; |
92dcc234 SB |
175 | n = write(tpm_pt->cancel_fd, "-", 1); |
176 | if (n != 1) { | |
27215a22 | 177 | error_report("Canceling TPM command failed: %s", |
92dcc234 | 178 | strerror(errno)); |
92dcc234 SB |
179 | } |
180 | } else { | |
181 | error_report("Cannot cancel TPM command due to missing " | |
182 | "TPM sysfs cancel entry"); | |
183 | } | |
184 | } | |
4549a8b7 SB |
185 | } |
186 | ||
116694c3 SB |
187 | static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb) |
188 | { | |
56a3c24f | 189 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
4549a8b7 | 190 | |
56a3c24f | 191 | return tpm_pt->tpm_version; |
4549a8b7 SB |
192 | } |
193 | ||
b21e6aaf SB |
194 | static size_t tpm_passthrough_get_buffer_size(TPMBackend *tb) |
195 | { | |
abc5cda0 SB |
196 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); |
197 | int ret; | |
198 | ||
199 | ret = tpm_util_get_buffer_size(tpm_pt->tpm_fd, tpm_pt->tpm_version, | |
200 | &tpm_pt->tpm_buffersize); | |
201 | if (ret < 0) { | |
202 | tpm_pt->tpm_buffersize = 4096; | |
203 | } | |
204 | return tpm_pt->tpm_buffersize; | |
b21e6aaf SB |
205 | } |
206 | ||
92dcc234 SB |
207 | /* |
208 | * Unless path or file descriptor set has been provided by user, | |
209 | * determine the sysfs cancel file following kernel documentation | |
210 | * in Documentation/ABI/stable/sysfs-class-tpm. | |
05b71fb2 MAL |
211 | * From /dev/tpm0 create /sys/class/tpm/tpm0/device/cancel |
212 | * before 4.0: /sys/class/misc/tpm0/device/cancel | |
92dcc234 | 213 | */ |
f59864ba | 214 | static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt) |
92dcc234 SB |
215 | { |
216 | int fd = -1; | |
8e36d6ca | 217 | char *dev; |
92dcc234 | 218 | char path[PATH_MAX]; |
92dcc234 | 219 | |
f59864ba AV |
220 | if (tpm_pt->options->cancel_path) { |
221 | fd = qemu_open(tpm_pt->options->cancel_path, O_WRONLY); | |
92dcc234 | 222 | if (fd < 0) { |
05b71fb2 | 223 | error_report("tpm_passthrough: Could not open TPM cancel path: %s", |
92dcc234 SB |
224 | strerror(errno)); |
225 | } | |
226 | return fd; | |
227 | } | |
228 | ||
8e36d6ca | 229 | dev = strrchr(tpm_pt->tpm_dev, '/'); |
05b71fb2 MAL |
230 | if (!dev) { |
231 | error_report("tpm_passthrough: Bad TPM device path %s", | |
232 | tpm_pt->tpm_dev); | |
233 | return -1; | |
234 | } | |
235 | ||
236 | dev++; | |
237 | if (snprintf(path, sizeof(path), "/sys/class/tpm/%s/device/cancel", | |
238 | dev) < sizeof(path)) { | |
239 | fd = qemu_open(path, O_WRONLY); | |
240 | if (fd < 0) { | |
241 | if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", | |
242 | dev) < sizeof(path)) { | |
243 | fd = qemu_open(path, O_WRONLY); | |
8e36d6ca | 244 | } |
92dcc234 | 245 | } |
05b71fb2 MAL |
246 | } |
247 | ||
248 | if (fd < 0) { | |
249 | error_report("tpm_passthrough: Could not guess TPM cancel path"); | |
8e36d6ca | 250 | } else { |
05b71fb2 | 251 | tpm_pt->options->cancel_path = g_strdup(path); |
92dcc234 SB |
252 | } |
253 | ||
254 | return fd; | |
255 | } | |
256 | ||
803de211 MAL |
257 | static int |
258 | tpm_passthrough_handle_device_opts(TPMPassthruState *tpm_pt, QemuOpts *opts) | |
4549a8b7 SB |
259 | { |
260 | const char *value; | |
261 | ||
92dcc234 | 262 | value = qemu_opt_get(opts, "cancel-path"); |
f59864ba AV |
263 | if (value) { |
264 | tpm_pt->options->cancel_path = g_strdup(value); | |
265 | tpm_pt->options->has_cancel_path = true; | |
266 | } | |
92dcc234 | 267 | |
4549a8b7 | 268 | value = qemu_opt_get(opts, "path"); |
f59864ba AV |
269 | if (value) { |
270 | tpm_pt->options->has_path = true; | |
271 | tpm_pt->options->path = g_strdup(value); | |
4549a8b7 SB |
272 | } |
273 | ||
f59864ba | 274 | tpm_pt->tpm_dev = value ? value : TPM_PASSTHROUGH_DEFAULT_DEVICE; |
8f0605cc SB |
275 | tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR); |
276 | if (tpm_pt->tpm_fd < 0) { | |
27215a22 | 277 | error_report("Cannot access TPM device using '%s': %s", |
8f0605cc | 278 | tpm_pt->tpm_dev, strerror(errno)); |
bef2ed3f | 279 | return -1; |
4549a8b7 SB |
280 | } |
281 | ||
56a3c24f | 282 | if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) { |
27215a22 | 283 | error_report("'%s' is not a TPM device.", |
8f0605cc | 284 | tpm_pt->tpm_dev); |
bef2ed3f | 285 | return -1; |
4549a8b7 SB |
286 | } |
287 | ||
bef2ed3f MAL |
288 | tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tpm_pt); |
289 | if (tpm_pt->cancel_fd < 0) { | |
290 | return -1; | |
291 | } | |
4549a8b7 | 292 | |
bef2ed3f | 293 | return 0; |
4549a8b7 SB |
294 | } |
295 | ||
9f7c0ef2 | 296 | static TPMBackend *tpm_passthrough_create(QemuOpts *opts) |
4549a8b7 | 297 | { |
8f0605cc | 298 | Object *obj = object_new(TYPE_TPM_PASSTHROUGH); |
4549a8b7 | 299 | |
bef2ed3f MAL |
300 | if (tpm_passthrough_handle_device_opts(TPM_PASSTHROUGH(obj), opts)) { |
301 | object_unref(obj); | |
302 | return NULL; | |
92dcc234 SB |
303 | } |
304 | ||
9f7c0ef2 | 305 | return TPM_BACKEND(obj); |
4549a8b7 SB |
306 | } |
307 | ||
683c4b77 SB |
308 | static int tpm_passthrough_startup_tpm(TPMBackend *tb, size_t buffersize) |
309 | { | |
310 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); | |
311 | ||
312 | if (buffersize && buffersize < tpm_pt->tpm_buffersize) { | |
313 | error_report("Requested buffer size of %zu is smaller than host TPM's " | |
314 | "fixed buffer size of %zu", | |
315 | buffersize, tpm_pt->tpm_buffersize); | |
316 | return -1; | |
317 | } | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
f59864ba AV |
322 | static TpmTypeOptions *tpm_passthrough_get_tpm_options(TPMBackend *tb) |
323 | { | |
324 | TpmTypeOptions *options = g_new0(TpmTypeOptions, 1); | |
325 | ||
326 | options->type = TPM_TYPE_OPTIONS_KIND_PASSTHROUGH; | |
327 | options->u.passthrough.data = QAPI_CLONE(TPMPassthroughOptions, | |
328 | TPM_PASSTHROUGH(tb)->options); | |
329 | ||
330 | return options; | |
331 | } | |
332 | ||
bb716238 SB |
333 | static const QemuOptDesc tpm_passthrough_cmdline_opts[] = { |
334 | TPM_STANDARD_CMDLINE_OPTS, | |
335 | { | |
336 | .name = "cancel-path", | |
337 | .type = QEMU_OPT_STRING, | |
338 | .help = "Sysfs file entry for canceling TPM commands", | |
339 | }, | |
340 | { | |
341 | .name = "path", | |
342 | .type = QEMU_OPT_STRING, | |
343 | .help = "Path to TPM device on the host", | |
344 | }, | |
345 | { /* end of list */ }, | |
346 | }; | |
347 | ||
8f0605cc SB |
348 | static void tpm_passthrough_inst_init(Object *obj) |
349 | { | |
f35fe5cb AV |
350 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); |
351 | ||
f59864ba | 352 | tpm_pt->options = g_new0(TPMPassthroughOptions, 1); |
f35fe5cb AV |
353 | tpm_pt->tpm_fd = -1; |
354 | tpm_pt->cancel_fd = -1; | |
8f0605cc SB |
355 | } |
356 | ||
357 | static void tpm_passthrough_inst_finalize(Object *obj) | |
358 | { | |
f35fe5cb AV |
359 | TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(obj); |
360 | ||
361 | tpm_passthrough_cancel_cmd(TPM_BACKEND(obj)); | |
362 | ||
8df4d848 MAL |
363 | if (tpm_pt->tpm_fd >= 0) { |
364 | qemu_close(tpm_pt->tpm_fd); | |
365 | } | |
366 | if (tpm_pt->cancel_fd >= 0) { | |
367 | qemu_close(tpm_pt->cancel_fd); | |
368 | } | |
f59864ba | 369 | qapi_free_TPMPassthroughOptions(tpm_pt->options); |
8f0605cc SB |
370 | } |
371 | ||
372 | static void tpm_passthrough_class_init(ObjectClass *klass, void *data) | |
373 | { | |
374 | TPMBackendClass *tbc = TPM_BACKEND_CLASS(klass); | |
375 | ||
d31076ba MAL |
376 | tbc->type = TPM_TYPE_PASSTHROUGH; |
377 | tbc->opts = tpm_passthrough_cmdline_opts; | |
378 | tbc->desc = "Passthrough TPM backend driver"; | |
379 | tbc->create = tpm_passthrough_create; | |
683c4b77 | 380 | tbc->startup_tpm = tpm_passthrough_startup_tpm; |
d31076ba MAL |
381 | tbc->reset = tpm_passthrough_reset; |
382 | tbc->cancel_cmd = tpm_passthrough_cancel_cmd; | |
383 | tbc->get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag; | |
384 | tbc->reset_tpm_established_flag = | |
385 | tpm_passthrough_reset_tpm_established_flag; | |
386 | tbc->get_tpm_version = tpm_passthrough_get_tpm_version; | |
b21e6aaf | 387 | tbc->get_buffer_size = tpm_passthrough_get_buffer_size; |
d31076ba | 388 | tbc->get_tpm_options = tpm_passthrough_get_tpm_options; |
b19a5eea | 389 | tbc->handle_request = tpm_passthrough_handle_request; |
8f0605cc SB |
390 | } |
391 | ||
392 | static const TypeInfo tpm_passthrough_info = { | |
393 | .name = TYPE_TPM_PASSTHROUGH, | |
394 | .parent = TYPE_TPM_BACKEND, | |
395 | .instance_size = sizeof(TPMPassthruState), | |
396 | .class_init = tpm_passthrough_class_init, | |
397 | .instance_init = tpm_passthrough_inst_init, | |
398 | .instance_finalize = tpm_passthrough_inst_finalize, | |
399 | }; | |
400 | ||
4549a8b7 SB |
401 | static void tpm_passthrough_register(void) |
402 | { | |
8f0605cc | 403 | type_register_static(&tpm_passthrough_info); |
4549a8b7 SB |
404 | } |
405 | ||
406 | type_init(tpm_passthrough_register) |