// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2017, 2019, Linaro Limited * Copyright (c) 2020, Arm Limited. */ /* * Security properties of REE-FS TAs * ================================= * * Authentication only * ------------------- * * Required security properties: * 1. Authentication and non-repudiation of a TA to Service Provider (SP). * 2. Integrity of a TA. * * To satisfy (1) and (2), SP needs to sign TA and OP-TEE core needs to verify * the signature using SP public key with computed hash of the TA. * * Authentication along with Confidentiality * ----------------------------------------- * * Required security properties: * 1. Authentication and non-repudiation of a TA to Service Provider (SP). * 2. Confidentiality of a TA. * 3. Integrity of an encrypted TA blob. * * To satisfy (1), SP needs to sign plain TA and OP-TEE core needs to verify the * signature using SP public key with computed hash of the TA. * * To satisfy (2) and (3), SP needs to do authenticated encryption of TA and * OP-TEE core needs to do authenticated decryption of TA to retrieve its * contents. Here encryption provides the confidentiality of TA and MAC tag * provides the integrity of encrypted TA blob. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct ree_fs_ta_handle { struct shdr *nw_ta; /* Non-secure (shared memory) */ size_t nw_ta_size; struct mobj *mobj; size_t offs; struct shdr *shdr; /* Verified secure copy of @nw_ta's signed header */ void *hash_ctx; void *enc_ctx; struct shdr_bootstrap_ta *bs_hdr; struct shdr_encrypted_ta *ehdr; }; struct ver_db_entry { uint8_t uuid[sizeof(TEE_UUID)]; uint32_t version; }; struct ver_db_hdr { uint32_t db_version; uint32_t nb_entries; }; static const char ta_ver_db[] = "ta_ver.db"; static const char subkey_ver_db[] = "subkey_ver.db"; static struct mutex ver_db_mutex = MUTEX_INITIALIZER; static TEE_Result check_update_version(const char *db_name, const uint8_t uuid[sizeof(TEE_UUID)], uint32_t version) { struct ver_db_entry db_entry = { }; const struct tee_file_operations *ops = NULL; struct tee_file_handle *fh = NULL; TEE_Result res = TEE_SUCCESS; bool entry_found = false; size_t len = 0; unsigned int i = 0; struct ver_db_hdr db_hdr = { }; struct tee_pobj pobj = { .obj_id = (void *)db_name, .obj_id_len = strlen(db_name) + 1, }; ops = tee_svc_storage_file_ops(TEE_STORAGE_PRIVATE); if (!ops) return TEE_SUCCESS; /* Compiled with no secure storage */ mutex_lock(&ver_db_mutex); res = ops->open(&pobj, NULL, &fh); if (res != TEE_SUCCESS && res != TEE_ERROR_ITEM_NOT_FOUND) goto out; if (res == TEE_ERROR_ITEM_NOT_FOUND) { res = ops->create(&pobj, false, NULL, 0, NULL, 0, NULL, 0, &fh); if (res != TEE_SUCCESS) goto out; res = ops->write(fh, 0, &db_hdr, sizeof(db_hdr)); if (res != TEE_SUCCESS) goto out; } else { len = sizeof(db_hdr); res = ops->read(fh, 0, &db_hdr, &len); if (res != TEE_SUCCESS) { goto out; } else if (len != sizeof(db_hdr)) { res = TEE_ERROR_BAD_STATE; goto out; } } for (i = 0; i < db_hdr.nb_entries; i++) { len = sizeof(db_entry); res = ops->read(fh, sizeof(db_hdr) + (i * len), &db_entry, &len); if (res != TEE_SUCCESS) { goto out; } else if (len != sizeof(db_entry)) { res = TEE_ERROR_BAD_STATE; goto out; } if (!memcmp(uuid, db_entry.uuid, sizeof(TEE_UUID))) { entry_found = true; break; } } if (entry_found) { if (db_entry.version > version) { res = TEE_ERROR_ACCESS_CONFLICT; goto out; } else if (db_entry.version < version) { memcpy(db_entry.uuid, uuid, sizeof(TEE_UUID)); db_entry.version = version; len = sizeof(db_entry); res = ops->write(fh, sizeof(db_hdr) + (i * len), &db_entry, len); if (res != TEE_SUCCESS) goto out; } } else { memcpy(db_entry.uuid, uuid, sizeof(TEE_UUID)); db_entry.version = version; len = sizeof(db_entry); res = ops->write(fh, sizeof(db_hdr) + (db_hdr.nb_entries * len), &db_entry, len); if (res != TEE_SUCCESS) goto out; db_hdr.nb_entries++; res = ops->write(fh, 0, &db_hdr, sizeof(db_hdr)); if (res != TEE_SUCCESS) goto out; } out: ops->close(&fh); mutex_unlock(&ver_db_mutex); return res; } /* * Load a TA via RPC with UUID defined by input param @uuid. The virtual * address of the raw TA binary is received in out parameter @ta. */ static TEE_Result rpc_load(const TEE_UUID *uuid, struct shdr **ta, size_t *ta_size, struct mobj **mobj) { TEE_Result res; struct thread_param params[2]; if (!uuid || !ta || !mobj || !ta_size) return TEE_ERROR_BAD_PARAMETERS; memset(params, 0, sizeof(params)); params[0].attr = THREAD_PARAM_ATTR_VALUE_IN; tee_uuid_to_octets((void *)¶ms[0].u.value, uuid); params[1].attr = THREAD_PARAM_ATTR_MEMREF_OUT; res = thread_rpc_cmd(OPTEE_RPC_CMD_LOAD_TA, 2, params); if (res != TEE_SUCCESS) return res; *mobj = thread_rpc_alloc_payload(params[1].u.memref.size); if (!*mobj) return TEE_ERROR_OUT_OF_MEMORY; *ta = mobj_get_va(*mobj, 0, params[1].u.memref.size); if (!*ta) { res = TEE_ERROR_SHORT_BUFFER; goto exit; } /* We don't expect NULL as thread_rpc_alloc_payload() was successful */ assert(*ta); *ta_size = params[1].u.memref.size; params[0].attr = THREAD_PARAM_ATTR_VALUE_IN; tee_uuid_to_octets((void *)¶ms[0].u.value, uuid); params[1].attr = THREAD_PARAM_ATTR_MEMREF_OUT; params[1].u.memref.offs = 0; params[1].u.memref.mobj = *mobj; res = thread_rpc_cmd(OPTEE_RPC_CMD_LOAD_TA, 2, params); exit: if (res != TEE_SUCCESS) thread_rpc_free_payload(*mobj); return res; } static TEE_Result ree_fs_ta_open(const TEE_UUID *uuid, struct ts_store_handle **h) { uint8_t next_uuid[sizeof(TEE_UUID)] = { }; struct ree_fs_ta_handle *handle; uint8_t *next_uuid_ptr = NULL; struct shdr *shdr = NULL; struct mobj *mobj = NULL; void *hash_ctx = NULL; struct shdr *ta = NULL; size_t ta_size = 0; TEE_Result res = TEE_SUCCESS; size_t offs = 0; struct shdr_bootstrap_ta *bs_hdr = NULL; struct shdr_encrypted_ta *ehdr = NULL; size_t shdr_sz = 0; uint32_t max_depth = UINT32_MAX; struct ftmn ftmn = { }; unsigned int incr0_count = 0; handle = calloc(1, sizeof(*handle)); if (!handle) return TEE_ERROR_OUT_OF_MEMORY; /* Request TA from tee-supplicant */ res = rpc_load(uuid, &ta, &ta_size, &mobj); if (res != TEE_SUCCESS) goto error; /* Make secure copy of signed header */ shdr = shdr_alloc_and_copy(0, ta, ta_size); if (!shdr) { res = TEE_ERROR_SECURITY; goto error_free_payload; } /* Validate header signature */ FTMN_CALL_FUNC(res, &ftmn, FTMN_INCR0, shdr_verify_signature, shdr); incr0_count++; if (res != TEE_SUCCESS) goto error_free_payload; shdr_sz = SHDR_GET_SIZE(shdr); if (!shdr_sz) { res = TEE_ERROR_SECURITY; goto error_free_payload; } offs = shdr_sz; while (shdr->img_type == SHDR_SUBKEY) { struct shdr_pub_key pub_key = { }; if (offs > ta_size) { res = TEE_ERROR_SECURITY; goto error_free_payload; } res = shdr_load_pub_key(shdr, offs, (const void *)ta, ta_size, next_uuid_ptr, max_depth, &pub_key); if (res) goto error_free_payload; if (ADD_OVERFLOW(offs, shdr->img_size, &offs) || ADD_OVERFLOW(offs, pub_key.name_size, &offs) || offs > ta_size) { res = TEE_ERROR_SECURITY; goto error_free_payload; } max_depth = pub_key.max_depth; memcpy(next_uuid, pub_key.next_uuid, sizeof(TEE_UUID)); next_uuid_ptr = next_uuid; res = check_update_version(subkey_ver_db, pub_key.uuid, pub_key.version); if (res) { res = TEE_ERROR_SECURITY; shdr_free_pub_key(&pub_key); goto error_free_payload; } shdr_free(shdr); shdr = shdr_alloc_and_copy(offs, ta, ta_size); res = TEE_ERROR_SECURITY; if (shdr) { FTMN_CALL_FUNC(res, &ftmn, FTMN_INCR0, shdr_verify_signature2, &pub_key, shdr); incr0_count++; } shdr_free_pub_key(&pub_key); if (res) goto error_free_payload; shdr_sz = SHDR_GET_SIZE(shdr); if (!shdr_sz) { res = TEE_ERROR_SECURITY; goto error_free_payload; } offs += shdr_sz; if (offs > ta_size) { res = TEE_ERROR_SECURITY; goto error_free_payload; } } if (shdr->img_type != SHDR_TA && shdr->img_type != SHDR_BOOTSTRAP_TA && shdr->img_type != SHDR_ENCRYPTED_TA) { res = TEE_ERROR_SECURITY; goto error_free_payload; } /* * If we're verifying this TA using a subkey, make sure that * the UUID of the TA belongs to the namespace defined by the subkey. * The namespace is defined as in RFC4122, that is, valid UUID * is calculated as a V5 UUID SHA-512(subkey UUID, "name string"). */ if (next_uuid_ptr) { TEE_UUID check_uuid = { }; tee_uuid_from_octets(&check_uuid, next_uuid_ptr); if (memcmp(&check_uuid, uuid, sizeof(*uuid))) { res = TEE_ERROR_SECURITY; goto error_free_payload; } } /* * Initialize a hash context and run the algorithm over the signed * header (less the final file hash and its signature of course) */ res = crypto_hash_alloc_ctx(&hash_ctx, TEE_DIGEST_HASH_TO_ALGO(shdr->algo)); if (res != TEE_SUCCESS) goto error_free_payload; res = crypto_hash_init(hash_ctx); if (res != TEE_SUCCESS) goto error_free_hash; res = crypto_hash_update(hash_ctx, (uint8_t *)shdr, sizeof(*shdr)); if (res != TEE_SUCCESS) goto error_free_hash; if (shdr->img_type == SHDR_BOOTSTRAP_TA || shdr->img_type == SHDR_ENCRYPTED_TA) { TEE_UUID bs_uuid = { }; size_t sz = shdr_sz; if (ADD_OVERFLOW(sz, sizeof(*bs_hdr), &sz) || ta_size < sz) { res = TEE_ERROR_SECURITY; goto error_free_hash; } bs_hdr = malloc(sizeof(*bs_hdr)); if (!bs_hdr) { res = TEE_ERROR_OUT_OF_MEMORY; goto error_free_hash; } memcpy(bs_hdr, (uint8_t *)ta + offs, sizeof(*bs_hdr)); /* * There's a check later that the UUID embedded inside the * ELF is matching, but since we now have easy access to * the expected uuid of the TA we check it a bit earlier * here. */ tee_uuid_from_octets(&bs_uuid, bs_hdr->uuid); if (memcmp(&bs_uuid, uuid, sizeof(TEE_UUID))) { res = TEE_ERROR_SECURITY; goto error_free_hash; } res = crypto_hash_update(hash_ctx, (uint8_t *)bs_hdr, sizeof(*bs_hdr)); if (res != TEE_SUCCESS) goto error_free_hash; offs += sizeof(*bs_hdr); handle->bs_hdr = bs_hdr; } if (shdr->img_type == SHDR_ENCRYPTED_TA) { struct shdr_encrypted_ta img_ehdr = { }; size_t sz = shdr_sz; size_t ehdr_sz = 0; if (ADD_OVERFLOW(sz, sizeof(struct shdr_bootstrap_ta), &sz) || ADD_OVERFLOW(sz, sizeof(img_ehdr), &sz) || ta_size < sz) { res = TEE_ERROR_SECURITY; goto error_free_hash; } memcpy(&img_ehdr, ((uint8_t *)ta + offs), sizeof(img_ehdr)); ehdr_sz = SHDR_ENC_GET_SIZE(&img_ehdr); sz -= sizeof(img_ehdr); if (!ehdr_sz || ADD_OVERFLOW(sz, ehdr_sz, &sz) || ta_size < sz) { res = TEE_ERROR_SECURITY; goto error_free_hash; } /* * This is checked further down too, but we must sanity * check shdr->img_size before it's used below. */ if (ta_size != sz + shdr->img_size) { res = TEE_ERROR_SECURITY; goto error_free_hash; } ehdr = malloc(ehdr_sz); if (!ehdr) { res = TEE_ERROR_OUT_OF_MEMORY; goto error_free_hash; } *ehdr = img_ehdr; memcpy((uint8_t *)ehdr + sizeof(img_ehdr), (uint8_t *)ta + offs + sizeof(img_ehdr), ehdr_sz - sizeof(img_ehdr)); res = crypto_hash_update(hash_ctx, (uint8_t *)ehdr, ehdr_sz); if (res != TEE_SUCCESS) goto error_free_hash; res = tee_ta_decrypt_init(&handle->enc_ctx, ehdr, shdr->img_size); if (res != TEE_SUCCESS) goto error_free_hash; offs += ehdr_sz; handle->ehdr = ehdr; } if (ta_size != offs + shdr->img_size) { res = TEE_ERROR_SECURITY; goto error_free_hash; } handle->nw_ta = ta; handle->nw_ta_size = ta_size; handle->offs = offs; handle->hash_ctx = hash_ctx; handle->shdr = shdr; handle->mobj = mobj; *h = (struct ts_store_handle *)handle; FTMN_CALLEE_DONE_CHECK(&ftmn, FTMN_INCR1, FTMN_STEP_COUNT(incr0_count), TEE_SUCCESS); return TEE_SUCCESS; error_free_hash: crypto_hash_free_ctx(hash_ctx); error_free_payload: thread_rpc_free_payload(mobj); error: free(ehdr); free(bs_hdr); shdr_free(shdr); free(handle); FTMN_SET_CHECK_RES_NOT_ZERO(&ftmn, FTMN_INCR1, res); FTMN_CALLEE_DONE_CHECK(&ftmn, FTMN_INCR1, FTMN_STEP_COUNT(incr0_count, 1), res); return res; } static TEE_Result ree_fs_ta_get_size(const struct ts_store_handle *h, size_t *size) { struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h; *size = handle->shdr->img_size; return TEE_SUCCESS; } static TEE_Result ree_fs_ta_get_tag(const struct ts_store_handle *h, uint8_t *tag, unsigned int *tag_len) { struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h; if (!tag || *tag_len < handle->shdr->hash_size) { *tag_len = handle->shdr->hash_size; return TEE_ERROR_SHORT_BUFFER; } *tag_len = handle->shdr->hash_size; memcpy(tag, SHDR_GET_HASH(handle->shdr), handle->shdr->hash_size); return TEE_SUCCESS; } static TEE_Result check_digest(struct ree_fs_ta_handle *h) { void *digest = NULL; TEE_Result res; digest = malloc(h->shdr->hash_size); if (!digest) return TEE_ERROR_OUT_OF_MEMORY; res = crypto_hash_final(h->hash_ctx, digest, h->shdr->hash_size); if (res != TEE_SUCCESS) { res = TEE_ERROR_SECURITY; goto out; } if (FTMN_CALLEE_DONE_MEMCMP(memcmp, digest, SHDR_GET_HASH(h->shdr), h->shdr->hash_size)) res = TEE_ERROR_SECURITY; out: free(digest); return res; } static TEE_Result ree_fs_ta_read(struct ts_store_handle *h, void *data, size_t len) { struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h; uint8_t *src = (uint8_t *)handle->nw_ta + handle->offs; size_t next_offs = 0; uint8_t *dst = src; TEE_Result res = TEE_SUCCESS; if (ADD_OVERFLOW(handle->offs, len, &next_offs) || next_offs > handle->nw_ta_size) return TEE_ERROR_BAD_PARAMETERS; if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) { if (data) { dst = data; /* Hash secure buffer */ res = tee_ta_decrypt_update(handle->enc_ctx, dst, src, len); if (res != TEE_SUCCESS) return TEE_ERROR_SECURITY; } else { size_t num_bytes = 0; size_t b_size = MIN(1024U, len); uint8_t *b = malloc(b_size); if (!b) return TEE_ERROR_OUT_OF_MEMORY; dst = NULL; while (num_bytes < len) { size_t n = MIN(b_size, len - num_bytes); res = tee_ta_decrypt_update(handle->enc_ctx, b, src + num_bytes, n); if (res) break; num_bytes += n; res = crypto_hash_update(handle->hash_ctx, b, n); if (res) break; } free(b); if (res != TEE_SUCCESS) return TEE_ERROR_SECURITY; } } else if (data) { dst = data; /* Hash secure buffer (shm might be modified) */ memcpy(dst, src, len); } if (dst) { res = crypto_hash_update(handle->hash_ctx, dst, len); if (res != TEE_SUCCESS) return TEE_ERROR_SECURITY; } handle->offs = next_offs; if (handle->offs == handle->nw_ta_size) { if (handle->shdr->img_type == SHDR_ENCRYPTED_TA) { /* * Last read: time to finalize authenticated * decryption. */ res = tee_ta_decrypt_final(handle->enc_ctx, handle->ehdr, NULL, NULL, 0); if (res != TEE_SUCCESS) return TEE_ERROR_SECURITY; } /* * Last read: time to check if our digest matches the expected * one (from the signed header) */ res = check_digest(handle); if (res != TEE_SUCCESS) return res; if (handle->bs_hdr) res = check_update_version(ta_ver_db, handle->bs_hdr->uuid, handle->bs_hdr->ta_version); } return res; } static void ree_fs_ta_close(struct ts_store_handle *h) { struct ree_fs_ta_handle *handle = (struct ree_fs_ta_handle *)h; if (!handle) return; thread_rpc_free_payload(handle->mobj); crypto_hash_free_ctx(handle->hash_ctx); free(handle->shdr); free(handle->ehdr); free(handle->bs_hdr); free(handle); } #ifndef CFG_REE_FS_TA_BUFFERED REGISTER_TA_STORE(9) = { .description = "REE", .open = ree_fs_ta_open, .get_size = ree_fs_ta_get_size, .get_tag = ree_fs_ta_get_tag, .read = ree_fs_ta_read, .close = ree_fs_ta_close, }; #endif #ifdef CFG_REE_FS_TA_BUFFERED /* * This is a wrapper around the "REE FS" TA store. * The whole TA/library is read into a temporary buffer during .open(). This * allows the binary to be authenticated before any data is read and processed * by the upper layer (ELF loader). */ struct buf_ree_fs_ta_handle { struct ts_store_handle *h; /* Note: a REE FS TA store handle */ size_t ta_size; tee_mm_entry_t *mm; uint8_t *buf; size_t offs; uint8_t *tag; unsigned int tag_len; }; static TEE_Result buf_ta_open(const TEE_UUID *uuid, struct ts_store_handle **h) { struct buf_ree_fs_ta_handle *handle = NULL; struct ftmn ftmn = { }; TEE_Result res = TEE_SUCCESS; handle = calloc(1, sizeof(*handle)); if (!handle) return TEE_ERROR_OUT_OF_MEMORY; FTMN_PUSH_LINKED_CALL(&ftmn, FTMN_FUNC_HASH("ree_fs_ta_open")); res = ree_fs_ta_open(uuid, &handle->h); if (!res) FTMN_SET_CHECK_RES_FROM_CALL(&ftmn, FTMN_INCR0, res); FTMN_POP_LINKED_CALL(&ftmn); if (res) goto err_free_handle; ftmn_checkpoint(&ftmn, FTMN_INCR1); res = ree_fs_ta_get_size(handle->h, &handle->ta_size); if (res) goto err; res = ree_fs_ta_get_tag(handle->h, NULL, &handle->tag_len); if (res != TEE_ERROR_SHORT_BUFFER) { res = TEE_ERROR_GENERIC; goto err; } handle->tag = malloc(handle->tag_len); if (!handle->tag) { res = TEE_ERROR_OUT_OF_MEMORY; goto err; } res = ree_fs_ta_get_tag(handle->h, handle->tag, &handle->tag_len); if (res) goto err; handle->mm = tee_mm_alloc(&tee_mm_sec_ddr, handle->ta_size); if (!handle->mm) { res = TEE_ERROR_OUT_OF_MEMORY; goto err; } handle->buf = phys_to_virt(tee_mm_get_smem(handle->mm), MEM_AREA_TA_RAM, handle->ta_size); if (!handle->buf) { res = TEE_ERROR_OUT_OF_MEMORY; goto err; } FTMN_PUSH_LINKED_CALL(&ftmn, FTMN_FUNC_HASH("check_digest")); res = ree_fs_ta_read(handle->h, handle->buf, handle->ta_size); if (!res) FTMN_SET_CHECK_RES_FROM_CALL(&ftmn, FTMN_INCR0, res); FTMN_POP_LINKED_CALL(&ftmn); if (res) goto err; ftmn_checkpoint(&ftmn, FTMN_INCR1); *h = (struct ts_store_handle *)handle; ree_fs_ta_close(handle->h); return ftmn_return_res(&ftmn, FTMN_STEP_COUNT(2, 2), TEE_SUCCESS); err: ree_fs_ta_close(handle->h); tee_mm_free(handle->mm); free(handle->tag); err_free_handle: free(handle); return res; } static TEE_Result buf_ta_get_size(const struct ts_store_handle *h, size_t *size) { struct buf_ree_fs_ta_handle *handle = (struct buf_ree_fs_ta_handle *)h; *size = handle->ta_size; return TEE_SUCCESS; } static TEE_Result buf_ta_read(struct ts_store_handle *h, void *data, size_t len) { struct buf_ree_fs_ta_handle *handle = (struct buf_ree_fs_ta_handle *)h; uint8_t *src = handle->buf + handle->offs; size_t next_offs = 0; if (ADD_OVERFLOW(handle->offs, len, &next_offs) || next_offs > handle->ta_size) return TEE_ERROR_BAD_PARAMETERS; if (data) memcpy(data, src, len); handle->offs = next_offs; return TEE_SUCCESS; } static TEE_Result buf_ta_get_tag(const struct ts_store_handle *h, uint8_t *tag, unsigned int *tag_len) { struct buf_ree_fs_ta_handle *handle = (struct buf_ree_fs_ta_handle *)h; *tag_len = handle->tag_len; if (!tag || *tag_len < handle->tag_len) return TEE_ERROR_SHORT_BUFFER; memcpy(tag, handle->tag, handle->tag_len); return TEE_SUCCESS; } static void buf_ta_close(struct ts_store_handle *h) { struct buf_ree_fs_ta_handle *handle = (struct buf_ree_fs_ta_handle *)h; if (!handle) return; tee_mm_free(handle->mm); free(handle->tag); free(handle); } REGISTER_TA_STORE(9) = { .description = "REE [buffered]", .open = buf_ta_open, .get_size = buf_ta_get_size, .get_tag = buf_ta_get_tag, .read = buf_ta_read, .close = buf_ta_close, }; #endif /* CFG_REE_FS_TA_BUFFERED */