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