1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * EFI variable service via TEE
4 *
5 * Copyright (C) 2022 Linaro
6 */
7
8 #include <linux/efi.h>
9 #include <linux/kernel.h>
10 #include <linux/slab.h>
11 #include <linux/tee.h>
12 #include <linux/tee_drv.h>
13 #include <linux/ucs2_string.h>
14 #include "mm_communication.h"
15
16 static struct efivars tee_efivars;
17
18 static size_t max_buffer_size; /* comm + var + func + data */
19 static size_t max_payload_size; /* func + data */
20
21 struct tee_stmm_efi_private {
22 struct tee_context *ctx;
23 u32 session;
24 struct device *dev;
25 };
26
27 static struct tee_stmm_efi_private pvt_data;
28
29 /* UUID of the stmm PTA */
30 static const struct tee_client_device_id tee_stmm_efi_id_table[] = {
31 {PTA_STMM_UUID},
32 {}
33 };
34
tee_ctx_match(struct tee_ioctl_version_data * ver,const void * data)35 static int tee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
36 {
37 /* currently only OP-TEE is supported as a communication path */
38 if (ver->impl_id == TEE_IMPL_ID_OPTEE)
39 return 1;
40 else
41 return 0;
42 }
43
44 /**
45 * tee_mm_communicate() - Pass a buffer to StandaloneMM running in TEE
46 *
47 * @comm_buf: locally allocated communication buffer
48 * @dsize: buffer size
49 * Return: status code
50 */
tee_mm_communicate(void * comm_buf,size_t dsize)51 static efi_status_t tee_mm_communicate(void *comm_buf, size_t dsize)
52 {
53 size_t buf_size;
54 struct efi_mm_communicate_header *mm_hdr;
55 struct tee_ioctl_invoke_arg arg;
56 struct tee_param param[4];
57 struct tee_shm *shm = NULL;
58 int rc;
59
60 if (!comm_buf)
61 return EFI_INVALID_PARAMETER;
62
63 mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
64 buf_size = mm_hdr->message_len + sizeof(efi_guid_t) + sizeof(size_t);
65
66 if (dsize != buf_size)
67 return EFI_INVALID_PARAMETER;
68
69 shm = tee_shm_register_kernel_buf(pvt_data.ctx, comm_buf, buf_size);
70 if (IS_ERR(shm)) {
71 dev_err(pvt_data.dev, "Unable to register shared memory\n");
72 return EFI_UNSUPPORTED;
73 }
74
75 memset(&arg, 0, sizeof(arg));
76 arg.func = PTA_STMM_CMD_COMMUNICATE;
77 arg.session = pvt_data.session;
78 arg.num_params = 4;
79
80 memset(param, 0, sizeof(param));
81 param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT;
82 param[0].u.memref.size = buf_size;
83 param[0].u.memref.shm = shm;
84 param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;
85 param[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
86 param[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;
87
88 rc = tee_client_invoke_func(pvt_data.ctx, &arg, param);
89 tee_shm_free(shm);
90
91 if (rc < 0 || arg.ret != 0) {
92 dev_err(pvt_data.dev,
93 "PTA_STMM_CMD_COMMUNICATE invoke error: 0x%x\n", arg.ret);
94 return EFI_DEVICE_ERROR;
95 }
96
97 switch (param[1].u.value.a) {
98 case ARM_SVC_SPM_RET_SUCCESS:
99 return EFI_SUCCESS;
100
101 case ARM_SVC_SPM_RET_INVALID_PARAMS:
102 return EFI_INVALID_PARAMETER;
103
104 case ARM_SVC_SPM_RET_DENIED:
105 return EFI_ACCESS_DENIED;
106
107 case ARM_SVC_SPM_RET_NO_MEMORY:
108 return EFI_OUT_OF_RESOURCES;
109
110 default:
111 return EFI_ACCESS_DENIED;
112 }
113 }
114
115 /**
116 * mm_communicate() - Adjust the communication buffer to StandAlonneMM and send
117 * it to TEE
118 *
119 * @comm_buf: locally allocated communication buffer, buffer should
120 * be enough big to have some headers and payload
121 * @payload_size: payload size
122 * Return: status code
123 */
mm_communicate(u8 * comm_buf,size_t payload_size)124 static efi_status_t mm_communicate(u8 *comm_buf, size_t payload_size)
125 {
126 size_t dsize;
127 efi_status_t ret;
128 struct efi_mm_communicate_header *mm_hdr;
129 struct smm_variable_communicate_header *var_hdr;
130
131 dsize = payload_size + MM_COMMUNICATE_HEADER_SIZE +
132 MM_VARIABLE_COMMUNICATE_SIZE;
133 mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
134 var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;
135
136 ret = tee_mm_communicate(comm_buf, dsize);
137 if (ret != EFI_SUCCESS) {
138 dev_err(pvt_data.dev, "%s failed!\n", __func__);
139 return ret;
140 }
141
142 return var_hdr->ret_status;
143 }
144
145 #define COMM_BUF_SIZE(__payload_size) (MM_COMMUNICATE_HEADER_SIZE + \
146 MM_VARIABLE_COMMUNICATE_SIZE + \
147 (__payload_size))
148
149 /**
150 * setup_mm_hdr() - Allocate a buffer for StandAloneMM and initialize the
151 * header data.
152 *
153 * @dptr: pointer address to store allocated buffer
154 * @payload_size: payload size
155 * @func: standAloneMM function number
156 * Return: pointer to corresponding StandAloneMM function buffer or NULL
157 */
setup_mm_hdr(u8 ** dptr,size_t payload_size,size_t func)158 static void *setup_mm_hdr(u8 **dptr, size_t payload_size, size_t func)
159 {
160 const efi_guid_t mm_var_guid = EFI_MM_VARIABLE_GUID;
161 struct efi_mm_communicate_header *mm_hdr;
162 struct smm_variable_communicate_header *var_hdr;
163 u8 *comm_buf;
164
165 /* In the init function we initialize max_buffer_size with
166 * get_max_payload(). So skip the test if max_buffer_size is initialized
167 * StandAloneMM will perform similar checks and drop the buffer if it's
168 * too long
169 */
170 if (max_buffer_size &&
171 max_buffer_size < (MM_COMMUNICATE_HEADER_SIZE +
172 MM_VARIABLE_COMMUNICATE_SIZE + payload_size)) {
173 return NULL;
174 }
175
176 comm_buf = alloc_pages_exact(COMM_BUF_SIZE(payload_size),
177 GFP_KERNEL | __GFP_ZERO);
178 if (!comm_buf)
179 return NULL;
180
181 mm_hdr = (struct efi_mm_communicate_header *)comm_buf;
182 memcpy(&mm_hdr->header_guid, &mm_var_guid, sizeof(mm_hdr->header_guid));
183 mm_hdr->message_len = MM_VARIABLE_COMMUNICATE_SIZE + payload_size;
184
185 var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;
186 var_hdr->function = func;
187 *dptr = comm_buf;
188
189 return var_hdr->data;
190 }
191
192 /**
193 * get_max_payload() - Get variable payload size from StandAloneMM.
194 *
195 * @size: size of the variable in storage
196 * Return: status code
197 */
get_max_payload(size_t * size)198 static efi_status_t get_max_payload(size_t *size)
199 {
200 struct smm_variable_payload_size *var_payload = NULL;
201 size_t payload_size;
202 u8 *comm_buf = NULL;
203 efi_status_t ret;
204
205 if (!size)
206 return EFI_INVALID_PARAMETER;
207
208 payload_size = sizeof(*var_payload);
209 var_payload = setup_mm_hdr(&comm_buf, payload_size,
210 SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE);
211 if (!var_payload)
212 return EFI_DEVICE_ERROR;
213
214 ret = mm_communicate(comm_buf, payload_size);
215 if (ret != EFI_SUCCESS)
216 goto out;
217
218 /* Make sure the buffer is big enough for storing variables */
219 if (var_payload->size < MM_VARIABLE_ACCESS_HEADER_SIZE + 0x20) {
220 ret = EFI_DEVICE_ERROR;
221 goto out;
222 }
223 *size = var_payload->size;
224 /*
225 * There seems to be a bug in EDK2 miscalculating the boundaries and
226 * size checks, so deduct 2 more bytes to fulfill this requirement. Fix
227 * it up here to ensure backwards compatibility with older versions
228 * (cf. StandaloneMmPkg/Drivers/StandaloneMmCpu/AArch64/EventHandle.c.
229 * sizeof (EFI_MM_COMMUNICATE_HEADER) instead the size minus the
230 * flexible array member).
231 *
232 * size is guaranteed to be > 2 due to checks on the beginning.
233 */
234 *size -= 2;
235 out:
236 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
237 return ret;
238 }
239
get_property_int(u16 * name,size_t name_size,const efi_guid_t * vendor,struct var_check_property * var_property)240 static efi_status_t get_property_int(u16 *name, size_t name_size,
241 const efi_guid_t *vendor,
242 struct var_check_property *var_property)
243 {
244 struct smm_variable_var_check_property *smm_property;
245 size_t payload_size;
246 u8 *comm_buf = NULL;
247 efi_status_t ret;
248
249 memset(var_property, 0, sizeof(*var_property));
250 payload_size = sizeof(*smm_property) + name_size;
251 if (payload_size > max_payload_size)
252 return EFI_INVALID_PARAMETER;
253
254 smm_property = setup_mm_hdr(
255 &comm_buf, payload_size,
256 SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_GET);
257 if (!smm_property)
258 return EFI_DEVICE_ERROR;
259
260 memcpy(&smm_property->guid, vendor, sizeof(smm_property->guid));
261 smm_property->name_size = name_size;
262 memcpy(smm_property->name, name, name_size);
263
264 ret = mm_communicate(comm_buf, payload_size);
265 /*
266 * Currently only R/O property is supported in StMM.
267 * Variables that are not set to R/O will not set the property in StMM
268 * and the call will return EFI_NOT_FOUND. We are setting the
269 * properties to 0x0 so checking against that is enough for the
270 * EFI_NOT_FOUND case.
271 */
272 if (ret == EFI_NOT_FOUND)
273 ret = EFI_SUCCESS;
274 if (ret != EFI_SUCCESS)
275 goto out;
276 memcpy(var_property, &smm_property->property, sizeof(*var_property));
277
278 out:
279 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
280 return ret;
281 }
282
tee_get_variable(u16 * name,efi_guid_t * vendor,u32 * attributes,unsigned long * data_size,void * data)283 static efi_status_t tee_get_variable(u16 *name, efi_guid_t *vendor,
284 u32 *attributes, unsigned long *data_size,
285 void *data)
286 {
287 struct var_check_property var_property;
288 struct smm_variable_access *var_acc;
289 size_t payload_size;
290 size_t name_size;
291 size_t tmp_dsize;
292 u8 *comm_buf = NULL;
293 efi_status_t ret;
294
295 if (!name || !vendor || !data_size)
296 return EFI_INVALID_PARAMETER;
297
298 name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);
299 if (name_size > max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE)
300 return EFI_INVALID_PARAMETER;
301
302 /* Trim output buffer size */
303 tmp_dsize = *data_size;
304 if (name_size + tmp_dsize >
305 max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE) {
306 tmp_dsize = max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE -
307 name_size;
308 }
309
310 payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + tmp_dsize;
311 var_acc = setup_mm_hdr(&comm_buf, payload_size,
312 SMM_VARIABLE_FUNCTION_GET_VARIABLE);
313 if (!var_acc)
314 return EFI_DEVICE_ERROR;
315
316 /* Fill in contents */
317 memcpy(&var_acc->guid, vendor, sizeof(var_acc->guid));
318 var_acc->data_size = tmp_dsize;
319 var_acc->name_size = name_size;
320 var_acc->attr = attributes ? *attributes : 0;
321 memcpy(var_acc->name, name, name_size);
322
323 ret = mm_communicate(comm_buf, payload_size);
324 if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL)
325 /* Update with reported data size for trimmed case */
326 *data_size = var_acc->data_size;
327 if (ret != EFI_SUCCESS)
328 goto out;
329
330 ret = get_property_int(name, name_size, vendor, &var_property);
331 if (ret != EFI_SUCCESS)
332 goto out;
333
334 if (attributes)
335 *attributes = var_acc->attr;
336
337 if (!data) {
338 ret = EFI_INVALID_PARAMETER;
339 goto out;
340 }
341 memcpy(data, (u8 *)var_acc->name + var_acc->name_size,
342 var_acc->data_size);
343 out:
344 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
345 return ret;
346 }
347
tee_get_next_variable(unsigned long * name_size,efi_char16_t * name,efi_guid_t * guid)348 static efi_status_t tee_get_next_variable(unsigned long *name_size,
349 efi_char16_t *name, efi_guid_t *guid)
350 {
351 struct smm_variable_getnext *var_getnext;
352 size_t payload_size;
353 size_t out_name_size;
354 size_t in_name_size;
355 u8 *comm_buf = NULL;
356 efi_status_t ret;
357
358 if (!name_size || !name || !guid)
359 return EFI_INVALID_PARAMETER;
360
361 out_name_size = *name_size;
362 in_name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);
363
364 if (out_name_size < in_name_size)
365 return EFI_INVALID_PARAMETER;
366
367 if (in_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE)
368 return EFI_INVALID_PARAMETER;
369
370 /* Trim output buffer size */
371 if (out_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE)
372 out_name_size =
373 max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE;
374
375 payload_size = MM_VARIABLE_GET_NEXT_HEADER_SIZE + out_name_size;
376 var_getnext = setup_mm_hdr(&comm_buf, payload_size,
377 SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME);
378 if (!var_getnext)
379 return EFI_DEVICE_ERROR;
380
381 /* Fill in contents */
382 memcpy(&var_getnext->guid, guid, sizeof(var_getnext->guid));
383 var_getnext->name_size = out_name_size;
384 memcpy(var_getnext->name, name, in_name_size);
385 memset((u8 *)var_getnext->name + in_name_size, 0x0,
386 out_name_size - in_name_size);
387
388 ret = mm_communicate(comm_buf, payload_size);
389 if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) {
390 /* Update with reported data size for trimmed case */
391 *name_size = var_getnext->name_size;
392 }
393 if (ret != EFI_SUCCESS)
394 goto out;
395
396 memcpy(guid, &var_getnext->guid, sizeof(*guid));
397 memcpy(name, var_getnext->name, var_getnext->name_size);
398
399 out:
400 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
401 return ret;
402 }
403
tee_set_variable(efi_char16_t * name,efi_guid_t * vendor,u32 attributes,unsigned long data_size,void * data)404 static efi_status_t tee_set_variable(efi_char16_t *name, efi_guid_t *vendor,
405 u32 attributes, unsigned long data_size,
406 void *data)
407 {
408 efi_status_t ret;
409 struct var_check_property var_property;
410 struct smm_variable_access *var_acc;
411 size_t payload_size;
412 size_t name_size;
413 u8 *comm_buf = NULL;
414
415 if (!name || name[0] == 0 || !vendor)
416 return EFI_INVALID_PARAMETER;
417
418 if (data_size > 0 && !data)
419 return EFI_INVALID_PARAMETER;
420
421 /* Check payload size */
422 name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);
423 payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + data_size;
424 if (payload_size > max_payload_size)
425 return EFI_INVALID_PARAMETER;
426
427 /*
428 * Allocate the buffer early, before switching to RW (if needed)
429 * so we won't need to account for any failures in reading/setting
430 * the properties, if the allocation fails
431 */
432 var_acc = setup_mm_hdr(&comm_buf, payload_size,
433 SMM_VARIABLE_FUNCTION_SET_VARIABLE);
434 if (!var_acc)
435 return EFI_DEVICE_ERROR;
436
437 /*
438 * The API has the ability to override RO flags. If no RO check was
439 * requested switch the variable to RW for the duration of this call
440 */
441 ret = get_property_int(name, name_size, vendor, &var_property);
442 if (ret != EFI_SUCCESS) {
443 dev_err(pvt_data.dev, "Getting variable property failed\n");
444 goto out;
445 }
446
447 if (var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) {
448 ret = EFI_WRITE_PROTECTED;
449 goto out;
450 }
451
452 /* Fill in contents */
453 memcpy(&var_acc->guid, vendor, sizeof(var_acc->guid));
454 var_acc->data_size = data_size;
455 var_acc->name_size = name_size;
456 var_acc->attr = attributes;
457 memcpy(var_acc->name, name, name_size);
458 memcpy((u8 *)var_acc->name + name_size, data, data_size);
459
460 ret = mm_communicate(comm_buf, payload_size);
461 dev_dbg(pvt_data.dev, "Set Variable %s %d %lx\n", __FILE__, __LINE__, ret);
462 out:
463 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
464 return ret;
465 }
466
tee_set_variable_nonblocking(efi_char16_t * name,efi_guid_t * vendor,u32 attributes,unsigned long data_size,void * data)467 static efi_status_t tee_set_variable_nonblocking(efi_char16_t *name,
468 efi_guid_t *vendor,
469 u32 attributes,
470 unsigned long data_size,
471 void *data)
472 {
473 return EFI_UNSUPPORTED;
474 }
475
tee_query_variable_info(u32 attributes,u64 * max_variable_storage_size,u64 * remain_variable_storage_size,u64 * max_variable_size)476 static efi_status_t tee_query_variable_info(u32 attributes,
477 u64 *max_variable_storage_size,
478 u64 *remain_variable_storage_size,
479 u64 *max_variable_size)
480 {
481 struct smm_variable_query_info *mm_query_info;
482 size_t payload_size;
483 efi_status_t ret;
484 u8 *comm_buf;
485
486 payload_size = sizeof(*mm_query_info);
487 mm_query_info = setup_mm_hdr(&comm_buf, payload_size,
488 SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO);
489 if (!mm_query_info)
490 return EFI_DEVICE_ERROR;
491
492 mm_query_info->attr = attributes;
493 ret = mm_communicate(comm_buf, payload_size);
494 if (ret != EFI_SUCCESS)
495 goto out;
496 *max_variable_storage_size = mm_query_info->max_variable_storage;
497 *remain_variable_storage_size =
498 mm_query_info->remaining_variable_storage;
499 *max_variable_size = mm_query_info->max_variable_size;
500
501 out:
502 free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));
503 return ret;
504 }
505
tee_stmm_efi_close_context(void * data)506 static void tee_stmm_efi_close_context(void *data)
507 {
508 tee_client_close_context(pvt_data.ctx);
509 }
510
tee_stmm_efi_close_session(void * data)511 static void tee_stmm_efi_close_session(void *data)
512 {
513 tee_client_close_session(pvt_data.ctx, pvt_data.session);
514 }
515
tee_stmm_restore_efivars_generic_ops(void)516 static void tee_stmm_restore_efivars_generic_ops(void)
517 {
518 efivars_unregister(&tee_efivars);
519 efivars_generic_ops_register();
520 }
521
522 static const struct efivar_operations tee_efivar_ops = {
523 .get_variable = tee_get_variable,
524 .get_next_variable = tee_get_next_variable,
525 .set_variable = tee_set_variable,
526 .set_variable_nonblocking = tee_set_variable_nonblocking,
527 .query_variable_store = efi_query_variable_store,
528 .query_variable_info = tee_query_variable_info,
529 };
530
tee_stmm_efi_probe(struct tee_client_device * tee_dev)531 static int tee_stmm_efi_probe(struct tee_client_device *tee_dev)
532 {
533 struct device *dev = &tee_dev->dev;
534 struct tee_ioctl_open_session_arg sess_arg;
535 efi_status_t ret;
536 int rc;
537
538 pvt_data.ctx = tee_client_open_context(NULL, tee_ctx_match, NULL, NULL);
539 if (IS_ERR(pvt_data.ctx))
540 return -ENODEV;
541
542 rc = devm_add_action_or_reset(dev, tee_stmm_efi_close_context, NULL);
543 if (rc)
544 return rc;
545
546 /* Open session with StMM PTA */
547 memset(&sess_arg, 0, sizeof(sess_arg));
548 export_uuid(sess_arg.uuid, &tee_stmm_efi_id_table[0].uuid);
549 rc = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL);
550 if ((rc < 0) || (sess_arg.ret != 0)) {
551 dev_err(dev, "tee_client_open_session failed, err: %x\n",
552 sess_arg.ret);
553 return -EINVAL;
554 }
555 pvt_data.session = sess_arg.session;
556 pvt_data.dev = dev;
557 rc = devm_add_action_or_reset(dev, tee_stmm_efi_close_session, NULL);
558 if (rc)
559 return rc;
560
561 ret = get_max_payload(&max_payload_size);
562 if (ret != EFI_SUCCESS)
563 return -EIO;
564
565 max_buffer_size = MM_COMMUNICATE_HEADER_SIZE +
566 MM_VARIABLE_COMMUNICATE_SIZE +
567 max_payload_size;
568
569 efivars_generic_ops_unregister();
570 pr_info("Using TEE-based EFI runtime variable services\n");
571 efivars_register(&tee_efivars, &tee_efivar_ops);
572
573 return 0;
574 }
575
tee_stmm_efi_remove(struct tee_client_device * dev)576 static void tee_stmm_efi_remove(struct tee_client_device *dev)
577 {
578 tee_stmm_restore_efivars_generic_ops();
579 }
580
581 MODULE_DEVICE_TABLE(tee, tee_stmm_efi_id_table);
582
583 static struct tee_client_driver tee_stmm_efi_driver = {
584 .id_table = tee_stmm_efi_id_table,
585 .probe = tee_stmm_efi_probe,
586 .remove = tee_stmm_efi_remove,
587 .driver = {
588 .name = "tee-stmm-efi",
589 },
590 };
591
592 module_tee_client_driver(tee_stmm_efi_driver);
593
594 MODULE_LICENSE("GPL");
595 MODULE_AUTHOR("Ilias Apalodimas <ilias.apalodimas@linaro.org>");
596 MODULE_AUTHOR("Masahisa Kojima <masahisa.kojima@linaro.org>");
597 MODULE_DESCRIPTION("TEE based EFI runtime variable service driver");
598