brintos

brintos / linux-shallow public Read only

0
0
Text · 16.7 KiB · f741ca2 Raw
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