Path: blob/master/drivers/firmware/efi/stmm/tee_stmm_efi.c
26482 views
// SPDX-License-Identifier: GPL-2.0+1/*2* EFI variable service via TEE3*4* Copyright (C) 2022 Linaro5*/67#include <linux/efi.h>8#include <linux/kernel.h>9#include <linux/slab.h>10#include <linux/tee.h>11#include <linux/tee_drv.h>12#include <linux/ucs2_string.h>13#include "mm_communication.h"1415static struct efivars tee_efivars;16static struct efivar_operations tee_efivar_ops;1718static size_t max_buffer_size; /* comm + var + func + data */19static size_t max_payload_size; /* func + data */2021struct tee_stmm_efi_private {22struct tee_context *ctx;23u32 session;24struct device *dev;25};2627static struct tee_stmm_efi_private pvt_data;2829/* UUID of the stmm PTA */30static const struct tee_client_device_id tee_stmm_efi_id_table[] = {31{PTA_STMM_UUID},32{}33};3435static 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 */38if (ver->impl_id == TEE_IMPL_ID_OPTEE)39return 1;40else41return 0;42}4344/**45* tee_mm_communicate() - Pass a buffer to StandaloneMM running in TEE46*47* @comm_buf: locally allocated communication buffer48* @dsize: buffer size49* Return: status code50*/51static efi_status_t tee_mm_communicate(void *comm_buf, size_t dsize)52{53size_t buf_size;54struct efi_mm_communicate_header *mm_hdr;55struct tee_ioctl_invoke_arg arg;56struct tee_param param[4];57struct tee_shm *shm = NULL;58int rc;5960if (!comm_buf)61return EFI_INVALID_PARAMETER;6263mm_hdr = (struct efi_mm_communicate_header *)comm_buf;64buf_size = mm_hdr->message_len + sizeof(efi_guid_t) + sizeof(size_t);6566if (dsize != buf_size)67return EFI_INVALID_PARAMETER;6869shm = tee_shm_register_kernel_buf(pvt_data.ctx, comm_buf, buf_size);70if (IS_ERR(shm)) {71dev_err(pvt_data.dev, "Unable to register shared memory\n");72return EFI_UNSUPPORTED;73}7475memset(&arg, 0, sizeof(arg));76arg.func = PTA_STMM_CMD_COMMUNICATE;77arg.session = pvt_data.session;78arg.num_params = 4;7980memset(param, 0, sizeof(param));81param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT;82param[0].u.memref.size = buf_size;83param[0].u.memref.shm = shm;84param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT;85param[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;86param[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE;8788rc = tee_client_invoke_func(pvt_data.ctx, &arg, param);89tee_shm_free(shm);9091if (rc < 0 || arg.ret != 0) {92dev_err(pvt_data.dev,93"PTA_STMM_CMD_COMMUNICATE invoke error: 0x%x\n", arg.ret);94return EFI_DEVICE_ERROR;95}9697switch (param[1].u.value.a) {98case ARM_SVC_SPM_RET_SUCCESS:99return EFI_SUCCESS;100101case ARM_SVC_SPM_RET_INVALID_PARAMS:102return EFI_INVALID_PARAMETER;103104case ARM_SVC_SPM_RET_DENIED:105return EFI_ACCESS_DENIED;106107case ARM_SVC_SPM_RET_NO_MEMORY:108return EFI_OUT_OF_RESOURCES;109110default:111return EFI_ACCESS_DENIED;112}113}114115/**116* mm_communicate() - Adjust the communication buffer to StandAlonneMM and send117* it to TEE118*119* @comm_buf: locally allocated communication buffer, buffer should120* be enough big to have some headers and payload121* @payload_size: payload size122* Return: status code123*/124static efi_status_t mm_communicate(u8 *comm_buf, size_t payload_size)125{126size_t dsize;127efi_status_t ret;128struct efi_mm_communicate_header *mm_hdr;129struct smm_variable_communicate_header *var_hdr;130131dsize = payload_size + MM_COMMUNICATE_HEADER_SIZE +132MM_VARIABLE_COMMUNICATE_SIZE;133mm_hdr = (struct efi_mm_communicate_header *)comm_buf;134var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;135136ret = tee_mm_communicate(comm_buf, dsize);137if (ret != EFI_SUCCESS) {138dev_err(pvt_data.dev, "%s failed!\n", __func__);139return ret;140}141142return var_hdr->ret_status;143}144145#define COMM_BUF_SIZE(__payload_size) (MM_COMMUNICATE_HEADER_SIZE + \146MM_VARIABLE_COMMUNICATE_SIZE + \147(__payload_size))148149/**150* setup_mm_hdr() - Allocate a buffer for StandAloneMM and initialize the151* header data.152*153* @dptr: pointer address to store allocated buffer154* @payload_size: payload size155* @func: standAloneMM function number156* Return: pointer to corresponding StandAloneMM function buffer or NULL157*/158static void *setup_mm_hdr(u8 **dptr, size_t payload_size, size_t func)159{160const efi_guid_t mm_var_guid = EFI_MM_VARIABLE_GUID;161struct efi_mm_communicate_header *mm_hdr;162struct smm_variable_communicate_header *var_hdr;163u8 *comm_buf;164165/* In the init function we initialize max_buffer_size with166* get_max_payload(). So skip the test if max_buffer_size is initialized167* StandAloneMM will perform similar checks and drop the buffer if it's168* too long169*/170if (max_buffer_size &&171max_buffer_size < (MM_COMMUNICATE_HEADER_SIZE +172MM_VARIABLE_COMMUNICATE_SIZE + payload_size)) {173return NULL;174}175176comm_buf = alloc_pages_exact(COMM_BUF_SIZE(payload_size),177GFP_KERNEL | __GFP_ZERO);178if (!comm_buf)179return NULL;180181mm_hdr = (struct efi_mm_communicate_header *)comm_buf;182memcpy(&mm_hdr->header_guid, &mm_var_guid, sizeof(mm_hdr->header_guid));183mm_hdr->message_len = MM_VARIABLE_COMMUNICATE_SIZE + payload_size;184185var_hdr = (struct smm_variable_communicate_header *)mm_hdr->data;186var_hdr->function = func;187*dptr = comm_buf;188189return var_hdr->data;190}191192/**193* get_max_payload() - Get variable payload size from StandAloneMM.194*195* @size: size of the variable in storage196* Return: status code197*/198static efi_status_t get_max_payload(size_t *size)199{200struct smm_variable_payload_size *var_payload = NULL;201size_t payload_size;202u8 *comm_buf = NULL;203efi_status_t ret;204205if (!size)206return EFI_INVALID_PARAMETER;207208payload_size = sizeof(*var_payload);209var_payload = setup_mm_hdr(&comm_buf, payload_size,210SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE);211if (!var_payload)212return EFI_DEVICE_ERROR;213214ret = mm_communicate(comm_buf, payload_size);215if (ret != EFI_SUCCESS)216goto out;217218/* Make sure the buffer is big enough for storing variables */219if (var_payload->size < MM_VARIABLE_ACCESS_HEADER_SIZE + 0x20) {220ret = EFI_DEVICE_ERROR;221goto out;222}223*size = var_payload->size;224/*225* There seems to be a bug in EDK2 miscalculating the boundaries and226* size checks, so deduct 2 more bytes to fulfill this requirement. Fix227* it up here to ensure backwards compatibility with older versions228* (cf. StandaloneMmPkg/Drivers/StandaloneMmCpu/AArch64/EventHandle.c.229* sizeof (EFI_MM_COMMUNICATE_HEADER) instead the size minus the230* flexible array member).231*232* size is guaranteed to be > 2 due to checks on the beginning.233*/234*size -= 2;235out:236free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));237return ret;238}239240static efi_status_t get_property_int(u16 *name, size_t name_size,241const efi_guid_t *vendor,242struct var_check_property *var_property)243{244struct smm_variable_var_check_property *smm_property;245size_t payload_size;246u8 *comm_buf = NULL;247efi_status_t ret;248249memset(var_property, 0, sizeof(*var_property));250payload_size = sizeof(*smm_property) + name_size;251if (payload_size > max_payload_size)252return EFI_INVALID_PARAMETER;253254smm_property = setup_mm_hdr(255&comm_buf, payload_size,256SMM_VARIABLE_FUNCTION_VAR_CHECK_VARIABLE_PROPERTY_GET);257if (!smm_property)258return EFI_DEVICE_ERROR;259260memcpy(&smm_property->guid, vendor, sizeof(smm_property->guid));261smm_property->name_size = name_size;262memcpy(smm_property->name, name, name_size);263264ret = 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 StMM268* and the call will return EFI_NOT_FOUND. We are setting the269* properties to 0x0 so checking against that is enough for the270* EFI_NOT_FOUND case.271*/272if (ret == EFI_NOT_FOUND)273ret = EFI_SUCCESS;274if (ret != EFI_SUCCESS)275goto out;276memcpy(var_property, &smm_property->property, sizeof(*var_property));277278out:279free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));280return ret;281}282283static efi_status_t tee_get_variable(u16 *name, efi_guid_t *vendor,284u32 *attributes, unsigned long *data_size,285void *data)286{287struct var_check_property var_property;288struct smm_variable_access *var_acc;289size_t payload_size;290size_t name_size;291size_t tmp_dsize;292u8 *comm_buf = NULL;293efi_status_t ret;294295if (!name || !vendor || !data_size)296return EFI_INVALID_PARAMETER;297298name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);299if (name_size > max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE)300return EFI_INVALID_PARAMETER;301302/* Trim output buffer size */303tmp_dsize = *data_size;304if (name_size + tmp_dsize >305max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE) {306tmp_dsize = max_payload_size - MM_VARIABLE_ACCESS_HEADER_SIZE -307name_size;308}309310payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + tmp_dsize;311var_acc = setup_mm_hdr(&comm_buf, payload_size,312SMM_VARIABLE_FUNCTION_GET_VARIABLE);313if (!var_acc)314return EFI_DEVICE_ERROR;315316/* Fill in contents */317memcpy(&var_acc->guid, vendor, sizeof(var_acc->guid));318var_acc->data_size = tmp_dsize;319var_acc->name_size = name_size;320var_acc->attr = attributes ? *attributes : 0;321memcpy(var_acc->name, name, name_size);322323ret = mm_communicate(comm_buf, payload_size);324if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL)325/* Update with reported data size for trimmed case */326*data_size = var_acc->data_size;327if (ret != EFI_SUCCESS)328goto out;329330ret = get_property_int(name, name_size, vendor, &var_property);331if (ret != EFI_SUCCESS)332goto out;333334if (attributes)335*attributes = var_acc->attr;336337if (!data) {338ret = EFI_INVALID_PARAMETER;339goto out;340}341memcpy(data, (u8 *)var_acc->name + var_acc->name_size,342var_acc->data_size);343out:344free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));345return ret;346}347348static efi_status_t tee_get_next_variable(unsigned long *name_size,349efi_char16_t *name, efi_guid_t *guid)350{351struct smm_variable_getnext *var_getnext;352size_t payload_size;353size_t out_name_size;354size_t in_name_size;355u8 *comm_buf = NULL;356efi_status_t ret;357358if (!name_size || !name || !guid)359return EFI_INVALID_PARAMETER;360361out_name_size = *name_size;362in_name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);363364if (out_name_size < in_name_size)365return EFI_INVALID_PARAMETER;366367if (in_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE)368return EFI_INVALID_PARAMETER;369370/* Trim output buffer size */371if (out_name_size > max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE)372out_name_size =373max_payload_size - MM_VARIABLE_GET_NEXT_HEADER_SIZE;374375payload_size = MM_VARIABLE_GET_NEXT_HEADER_SIZE + out_name_size;376var_getnext = setup_mm_hdr(&comm_buf, payload_size,377SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME);378if (!var_getnext)379return EFI_DEVICE_ERROR;380381/* Fill in contents */382memcpy(&var_getnext->guid, guid, sizeof(var_getnext->guid));383var_getnext->name_size = out_name_size;384memcpy(var_getnext->name, name, in_name_size);385memset((u8 *)var_getnext->name + in_name_size, 0x0,386out_name_size - in_name_size);387388ret = mm_communicate(comm_buf, payload_size);389if (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}393if (ret != EFI_SUCCESS)394goto out;395396memcpy(guid, &var_getnext->guid, sizeof(*guid));397memcpy(name, var_getnext->name, var_getnext->name_size);398399out:400free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));401return ret;402}403404static efi_status_t tee_set_variable(efi_char16_t *name, efi_guid_t *vendor,405u32 attributes, unsigned long data_size,406void *data)407{408efi_status_t ret;409struct var_check_property var_property;410struct smm_variable_access *var_acc;411size_t payload_size;412size_t name_size;413u8 *comm_buf = NULL;414415if (!name || name[0] == 0 || !vendor)416return EFI_INVALID_PARAMETER;417418if (data_size > 0 && !data)419return EFI_INVALID_PARAMETER;420421/* Check payload size */422name_size = (ucs2_strnlen(name, EFI_VAR_NAME_LEN) + 1) * sizeof(u16);423payload_size = MM_VARIABLE_ACCESS_HEADER_SIZE + name_size + data_size;424if (payload_size > max_payload_size)425return EFI_INVALID_PARAMETER;426427/*428* Allocate the buffer early, before switching to RW (if needed)429* so we won't need to account for any failures in reading/setting430* the properties, if the allocation fails431*/432var_acc = setup_mm_hdr(&comm_buf, payload_size,433SMM_VARIABLE_FUNCTION_SET_VARIABLE);434if (!var_acc)435return EFI_DEVICE_ERROR;436437/*438* The API has the ability to override RO flags. If no RO check was439* requested switch the variable to RW for the duration of this call440*/441ret = get_property_int(name, name_size, vendor, &var_property);442if (ret != EFI_SUCCESS) {443dev_err(pvt_data.dev, "Getting variable property failed\n");444goto out;445}446447if (var_property.property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) {448ret = EFI_WRITE_PROTECTED;449goto out;450}451452/* Fill in contents */453memcpy(&var_acc->guid, vendor, sizeof(var_acc->guid));454var_acc->data_size = data_size;455var_acc->name_size = name_size;456var_acc->attr = attributes;457memcpy(var_acc->name, name, name_size);458memcpy((u8 *)var_acc->name + name_size, data, data_size);459460ret = mm_communicate(comm_buf, payload_size);461dev_dbg(pvt_data.dev, "Set Variable %s %d %lx\n", __FILE__, __LINE__, ret);462out:463free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));464return ret;465}466467static efi_status_t tee_set_variable_nonblocking(efi_char16_t *name,468efi_guid_t *vendor,469u32 attributes,470unsigned long data_size,471void *data)472{473return EFI_UNSUPPORTED;474}475476static efi_status_t tee_query_variable_info(u32 attributes,477u64 *max_variable_storage_size,478u64 *remain_variable_storage_size,479u64 *max_variable_size)480{481struct smm_variable_query_info *mm_query_info;482size_t payload_size;483efi_status_t ret;484u8 *comm_buf;485486payload_size = sizeof(*mm_query_info);487mm_query_info = setup_mm_hdr(&comm_buf, payload_size,488SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO);489if (!mm_query_info)490return EFI_DEVICE_ERROR;491492mm_query_info->attr = attributes;493ret = mm_communicate(comm_buf, payload_size);494if (ret != EFI_SUCCESS)495goto out;496*max_variable_storage_size = mm_query_info->max_variable_storage;497*remain_variable_storage_size =498mm_query_info->remaining_variable_storage;499*max_variable_size = mm_query_info->max_variable_size;500501out:502free_pages_exact(comm_buf, COMM_BUF_SIZE(payload_size));503return ret;504}505506static void tee_stmm_efi_close_context(void *data)507{508tee_client_close_context(pvt_data.ctx);509}510511static void tee_stmm_efi_close_session(void *data)512{513tee_client_close_session(pvt_data.ctx, pvt_data.session);514}515516static void tee_stmm_restore_efivars_generic_ops(void)517{518efivars_unregister(&tee_efivars);519efivars_generic_ops_register();520}521522static int tee_stmm_efi_probe(struct device *dev)523{524struct tee_ioctl_open_session_arg sess_arg;525efi_status_t ret;526int rc;527528pvt_data.ctx = tee_client_open_context(NULL, tee_ctx_match, NULL, NULL);529if (IS_ERR(pvt_data.ctx))530return -ENODEV;531532rc = devm_add_action_or_reset(dev, tee_stmm_efi_close_context, NULL);533if (rc)534return rc;535536/* Open session with StMM PTA */537memset(&sess_arg, 0, sizeof(sess_arg));538export_uuid(sess_arg.uuid, &tee_stmm_efi_id_table[0].uuid);539rc = tee_client_open_session(pvt_data.ctx, &sess_arg, NULL);540if ((rc < 0) || (sess_arg.ret != 0)) {541dev_err(dev, "tee_client_open_session failed, err: %x\n",542sess_arg.ret);543return -EINVAL;544}545pvt_data.session = sess_arg.session;546pvt_data.dev = dev;547rc = devm_add_action_or_reset(dev, tee_stmm_efi_close_session, NULL);548if (rc)549return rc;550551ret = get_max_payload(&max_payload_size);552if (ret != EFI_SUCCESS)553return -EIO;554555max_buffer_size = MM_COMMUNICATE_HEADER_SIZE +556MM_VARIABLE_COMMUNICATE_SIZE +557max_payload_size;558559tee_efivar_ops.get_variable = tee_get_variable;560tee_efivar_ops.get_next_variable = tee_get_next_variable;561tee_efivar_ops.set_variable = tee_set_variable;562tee_efivar_ops.set_variable_nonblocking = tee_set_variable_nonblocking;563tee_efivar_ops.query_variable_store = efi_query_variable_store;564tee_efivar_ops.query_variable_info = tee_query_variable_info;565566efivars_generic_ops_unregister();567pr_info("Using TEE-based EFI runtime variable services\n");568efivars_register(&tee_efivars, &tee_efivar_ops);569570return 0;571}572573static int tee_stmm_efi_remove(struct device *dev)574{575tee_stmm_restore_efivars_generic_ops();576577return 0;578}579580MODULE_DEVICE_TABLE(tee, tee_stmm_efi_id_table);581582static struct tee_client_driver tee_stmm_efi_driver = {583.id_table = tee_stmm_efi_id_table,584.driver = {585.name = "tee-stmm-efi",586.bus = &tee_bus_type,587.probe = tee_stmm_efi_probe,588.remove = tee_stmm_efi_remove,589},590};591592static int __init tee_stmm_efi_mod_init(void)593{594return driver_register(&tee_stmm_efi_driver.driver);595}596597static void __exit tee_stmm_efi_mod_exit(void)598{599driver_unregister(&tee_stmm_efi_driver.driver);600}601602module_init(tee_stmm_efi_mod_init);603module_exit(tee_stmm_efi_mod_exit);604605MODULE_LICENSE("GPL");606MODULE_AUTHOR("Ilias Apalodimas <[email protected]>");607MODULE_AUTHOR("Masahisa Kojima <[email protected]>");608MODULE_DESCRIPTION("TEE based EFI runtime variable service driver");609610611