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