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