// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2014, STMicroelectronics International N.V. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RPMB_STORAGE_START_ADDRESS 0 #define RPMB_FS_FAT_START_ADDRESS 512 #define RPMB_BLOCK_SIZE_SHIFT 8 #define RPMB_CID_PRV_OFFSET 9 #define RPMB_CID_CRC_OFFSET 15 #define RPMB_FS_MAGIC 0x52504D42 #define FS_VERSION 2 #define FILE_IS_ACTIVE (1u << 0) #define FILE_IS_LAST_ENTRY (1u << 1) #define TEE_RPMB_FS_FILENAME_LENGTH 224 /** * FS parameters: Information often used by internal functions. * fat_start_address will be set by rpmb_fs_setup(). * rpmb_fs_parameters can be read by any other function. */ struct rpmb_fs_parameters { uint32_t fat_start_address; uint32_t max_rpmb_address; }; /** * File entry for a single file in a RPMB_FS partition. */ struct rpmb_fat_entry { uint32_t start_address; uint32_t data_size; uint32_t flags; uint32_t write_counter; uint8_t fek[TEE_FS_KM_FEK_SIZE]; char filename[TEE_RPMB_FS_FILENAME_LENGTH]; }; /** * FAT entry context with reference to a FAT entry and its * location in RPMB. */ struct rpmb_file_handle { struct rpmb_fat_entry fat_entry; const TEE_UUID *uuid; char filename[TEE_RPMB_FS_FILENAME_LENGTH]; /* Address for current entry in RPMB */ uint32_t rpmb_fat_address; }; /** * RPMB_FS partition data */ struct rpmb_fs_partition { uint32_t rpmb_fs_magic; uint32_t fs_version; uint32_t write_counter; uint32_t fat_start_address; /* Do not use reserved[] for other purpose than partition data. */ uint8_t reserved[112]; }; /** * A node in a list of directory entries. */ struct tee_rpmb_fs_dirent { struct tee_fs_dirent entry; SIMPLEQ_ENTRY(tee_rpmb_fs_dirent) link; }; /** * The RPMB directory representation. It contains a queue of * RPMB directory entries: 'next'. * The current pointer points to the last directory entry * returned by readdir(). */ struct tee_fs_dir { struct tee_rpmb_fs_dirent *current; /* */ SIMPLEQ_HEAD(next_head, tee_rpmb_fs_dirent) next; }; static struct rpmb_fs_parameters *fs_par; /* * Lower interface to RPMB device */ #define RPMB_DATA_OFFSET (RPMB_STUFF_DATA_SIZE + RPMB_KEY_MAC_SIZE) #define RPMB_MAC_PROTECT_DATA_SIZE (RPMB_DATA_FRAME_SIZE - RPMB_DATA_OFFSET) #define RPMB_MSG_TYPE_REQ_AUTH_KEY_PROGRAM 0x0001 #define RPMB_MSG_TYPE_REQ_WRITE_COUNTER_VAL_READ 0x0002 #define RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE 0x0003 #define RPMB_MSG_TYPE_REQ_AUTH_DATA_READ 0x0004 #define RPMB_MSG_TYPE_REQ_RESULT_READ 0x0005 #define RPMB_MSG_TYPE_RESP_AUTH_KEY_PROGRAM 0x0100 #define RPMB_MSG_TYPE_RESP_WRITE_COUNTER_VAL_READ 0x0200 #define RPMB_MSG_TYPE_RESP_AUTH_DATA_WRITE 0x0300 #define RPMB_MSG_TYPE_RESP_AUTH_DATA_READ 0x0400 #define RPMB_STUFF_DATA_SIZE 196 #define RPMB_KEY_MAC_SIZE 32 #define RPMB_DATA_SIZE 256 #define RPMB_NONCE_SIZE 16 #define RPMB_DATA_FRAME_SIZE 512 #define RPMB_RESULT_OK 0x00 #define RPMB_RESULT_GENERAL_FAILURE 0x01 #define RPMB_RESULT_AUTH_FAILURE 0x02 #define RPMB_RESULT_COUNTER_FAILURE 0x03 #define RPMB_RESULT_ADDRESS_FAILURE 0x04 #define RPMB_RESULT_WRITE_FAILURE 0x05 #define RPMB_RESULT_READ_FAILURE 0x06 #define RPMB_RESULT_AUTH_KEY_NOT_PROGRAMMED 0x07 #define RPMB_RESULT_MASK 0x3F #define RPMB_RESULT_WR_CNT_EXPIRED 0x80 /* RPMB internal commands */ #define RPMB_CMD_DATA_REQ 0x00 #define RPMB_CMD_GET_DEV_INFO 0x01 #define RPMB_SIZE_SINGLE (128 * 1024) /* Error codes for get_dev_info request/response. */ #define RPMB_CMD_GET_DEV_INFO_RET_OK 0x00 #define RPMB_CMD_GET_DEV_INFO_RET_ERROR 0x01 struct rpmb_data_frame { uint8_t stuff_bytes[RPMB_STUFF_DATA_SIZE]; uint8_t key_mac[RPMB_KEY_MAC_SIZE]; uint8_t data[RPMB_DATA_SIZE]; uint8_t nonce[RPMB_NONCE_SIZE]; uint8_t write_counter[4]; uint8_t address[2]; uint8_t block_count[2]; uint8_t op_result[2]; uint8_t msg_type[2]; }; struct rpmb_req { uint16_t cmd; uint16_t dev_id; uint16_t block_count; /* variable length of data */ /* uint8_t data[]; REMOVED! */ }; #define TEE_RPMB_REQ_DATA(req) \ ((void *)((struct rpmb_req *)(req) + 1)) struct rpmb_raw_data { uint16_t msg_type; uint16_t *op_result; uint16_t *block_count; uint16_t *blk_idx; uint32_t *write_counter; uint8_t *nonce; uint8_t *key_mac; uint8_t *data; /* data length to read or write */ uint32_t len; /* Byte address offset in the first block involved */ uint8_t byte_offset; }; #define RPMB_EMMC_CID_SIZE 16 struct rpmb_dev_info { uint8_t cid[RPMB_EMMC_CID_SIZE]; /* EXT CSD-slice 168 "RPMB Size" */ uint8_t rpmb_size_mult; /* EXT CSD-slice 222 "Reliable Write Sector Count" */ uint8_t rel_wr_sec_c; /* Check the ret code and accept the data only if it is OK. */ uint8_t ret_code; }; /* * Struct for rpmb context data. * * @key RPMB key. * @cid eMMC card ID. * @wr_cnt Current write counter. * @max_blk_idx The highest block index supported by current device. * @rel_wr_blkcnt Max number of data blocks for each reliable write. * @dev_id Device ID of the eMMC device. * @wr_cnt_synced Flag indicating if write counter is synced to RPMB. * @key_derived Flag indicating if key has been generated. * @key_verified Flag indicating the key generated is verified ok. * @dev_info_synced Flag indicating if dev info has been retrieved from RPMB. */ struct tee_rpmb_ctx { uint8_t key[RPMB_KEY_MAC_SIZE]; uint8_t cid[RPMB_EMMC_CID_SIZE]; uint32_t wr_cnt; uint16_t max_blk_idx; uint16_t rel_wr_blkcnt; uint16_t dev_id; bool wr_cnt_synced; bool key_derived; bool key_verified; bool dev_info_synced; }; static struct tee_rpmb_ctx *rpmb_ctx; /* * Mutex to serialize the operations exported by this file. * It protects rpmb_ctx and prevents overlapping operations on eMMC devices with * different IDs. */ static struct mutex rpmb_mutex = MUTEX_INITIALIZER; #ifdef CFG_RPMB_TESTKEY static const uint8_t rpmb_test_key[RPMB_KEY_MAC_SIZE] = { 0xD3, 0xEB, 0x3E, 0xC3, 0x6E, 0x33, 0x4C, 0x9F, 0x98, 0x8C, 0xE2, 0xC0, 0xB8, 0x59, 0x54, 0x61, 0x0D, 0x2B, 0xCF, 0x86, 0x64, 0x84, 0x4D, 0xF2, 0xAB, 0x56, 0xE6, 0xC6, 0x1B, 0xB7, 0x01, 0xE4 }; static TEE_Result tee_rpmb_key_gen(uint16_t dev_id __unused, uint8_t *key, uint32_t len) { TEE_Result res = TEE_SUCCESS; if (!key || RPMB_KEY_MAC_SIZE != len) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } DMSG("RPMB: Using test key"); memcpy(key, rpmb_test_key, RPMB_KEY_MAC_SIZE); out: return res; } #else /* !CFG_RPMB_TESTKEY */ static TEE_Result tee_rpmb_key_gen(uint16_t dev_id __unused, uint8_t *key, uint32_t len) { uint8_t message[RPMB_EMMC_CID_SIZE]; if (!key || RPMB_KEY_MAC_SIZE != len) return TEE_ERROR_BAD_PARAMETERS; IMSG("RPMB: Using generated key"); /* * PRV/CRC would be changed when doing eMMC FFU * The following fields should be masked off when deriving RPMB key * * CID [55: 48]: PRV (Product revision) * CID [07: 01]: CRC (CRC7 checksum) * CID [00]: not used */ memcpy(message, rpmb_ctx->cid, RPMB_EMMC_CID_SIZE); memset(message + RPMB_CID_PRV_OFFSET, 0, 1); memset(message + RPMB_CID_CRC_OFFSET, 0, 1); return huk_subkey_derive(HUK_SUBKEY_RPMB, message, sizeof(message), key, len); } #endif /* !CFG_RPMB_TESTKEY */ static void u32_to_bytes(uint32_t u32, uint8_t *bytes) { *bytes = (uint8_t) (u32 >> 24); *(bytes + 1) = (uint8_t) (u32 >> 16); *(bytes + 2) = (uint8_t) (u32 >> 8); *(bytes + 3) = (uint8_t) u32; } static void bytes_to_u32(uint8_t *bytes, uint32_t *u32) { *u32 = (uint32_t) ((*(bytes) << 24) + (*(bytes + 1) << 16) + (*(bytes + 2) << 8) + (*(bytes + 3))); } static void u16_to_bytes(uint16_t u16, uint8_t *bytes) { *bytes = (uint8_t) (u16 >> 8); *(bytes + 1) = (uint8_t) u16; } static void bytes_to_u16(uint8_t *bytes, uint16_t *u16) { *u16 = (uint16_t) ((*bytes << 8) + *(bytes + 1)); } static void get_op_result_bits(uint8_t *bytes, uint8_t *res) { *res = *(bytes + 1) & RPMB_RESULT_MASK; } static TEE_Result tee_rpmb_mac_calc(uint8_t *mac, uint32_t macsize, uint8_t *key, uint32_t keysize, struct rpmb_data_frame *datafrms, uint16_t blkcnt) { TEE_Result res = TEE_ERROR_GENERIC; int i; void *ctx = NULL; if (!mac || !key || !datafrms) return TEE_ERROR_BAD_PARAMETERS; res = crypto_mac_alloc_ctx(&ctx, TEE_ALG_HMAC_SHA256); if (res) return res; res = crypto_mac_init(ctx, key, keysize); if (res != TEE_SUCCESS) goto func_exit; for (i = 0; i < blkcnt; i++) { res = crypto_mac_update(ctx, datafrms[i].data, RPMB_MAC_PROTECT_DATA_SIZE); if (res != TEE_SUCCESS) goto func_exit; } res = crypto_mac_final(ctx, mac, macsize); if (res != TEE_SUCCESS) goto func_exit; res = TEE_SUCCESS; func_exit: crypto_mac_free_ctx(ctx); return res; } struct tee_rpmb_mem { struct mobj *phreq_mobj; struct mobj *phresp_mobj; size_t req_size; size_t resp_size; }; static void tee_rpmb_free(struct tee_rpmb_mem *mem) { if (!mem) return; if (mem->phreq_mobj) { thread_rpc_free_payload(mem->phreq_mobj); mem->phreq_mobj = NULL; } if (mem->phresp_mobj) { thread_rpc_free_payload(mem->phresp_mobj); mem->phresp_mobj = NULL; } } static TEE_Result tee_rpmb_alloc(size_t req_size, size_t resp_size, struct tee_rpmb_mem *mem, void **req, void **resp) { TEE_Result res = TEE_SUCCESS; size_t req_s = ROUNDUP(req_size, sizeof(uint32_t)); size_t resp_s = ROUNDUP(resp_size, sizeof(uint32_t)); if (!mem) return TEE_ERROR_BAD_PARAMETERS; memset(mem, 0, sizeof(*mem)); mem->phreq_mobj = thread_rpc_alloc_payload(req_s); mem->phresp_mobj = thread_rpc_alloc_payload(resp_s); if (!mem->phreq_mobj || !mem->phresp_mobj) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } *req = mobj_get_va(mem->phreq_mobj, 0); *resp = mobj_get_va(mem->phresp_mobj, 0); if (!*req || !*resp) { res = TEE_ERROR_GENERIC; goto out; } mem->req_size = req_size; mem->resp_size = resp_size; out: if (res != TEE_SUCCESS) tee_rpmb_free(mem); return res; } static TEE_Result tee_rpmb_invoke(struct tee_rpmb_mem *mem) { struct thread_param params[2] = { [0] = THREAD_PARAM_MEMREF(IN, mem->phreq_mobj, 0, mem->req_size), [1] = THREAD_PARAM_MEMREF(OUT, mem->phresp_mobj, 0, mem->resp_size), }; return thread_rpc_cmd(OPTEE_RPC_CMD_RPMB, 2, params); } static bool is_zero(const uint8_t *buf, size_t size) { size_t i; for (i = 0; i < size; i++) if (buf[i]) return false; return true; } static TEE_Result encrypt_block(uint8_t *out, const uint8_t *in, uint16_t blk_idx, const uint8_t *fek, const TEE_UUID *uuid) { return tee_fs_crypt_block(uuid, out, in, RPMB_DATA_SIZE, blk_idx, fek, TEE_MODE_ENCRYPT); } static TEE_Result decrypt_block(uint8_t *out, const uint8_t *in, uint16_t blk_idx, const uint8_t *fek, const TEE_UUID *uuid) { return tee_fs_crypt_block(uuid, out, in, RPMB_DATA_SIZE, blk_idx, fek, TEE_MODE_DECRYPT); } /* Decrypt/copy at most one block of data */ static TEE_Result decrypt(uint8_t *out, const struct rpmb_data_frame *frm, size_t size, size_t offset, uint16_t blk_idx __maybe_unused, const uint8_t *fek, const TEE_UUID *uuid) { uint8_t *tmp __maybe_unused; if ((size + offset < size) || (size + offset > RPMB_DATA_SIZE)) panic("invalid size or offset"); if (!fek) { /* Block is not encrypted (not a file data block) */ memcpy(out, frm->data + offset, size); } else if (is_zero(fek, TEE_FS_KM_FEK_SIZE)) { /* The file was created with encryption disabled */ return TEE_ERROR_SECURITY; } else { /* Block is encrypted */ if (size < RPMB_DATA_SIZE) { /* * Since output buffer is not large enough to hold one * block we must allocate a temporary buffer. */ tmp = malloc(RPMB_DATA_SIZE); if (!tmp) return TEE_ERROR_OUT_OF_MEMORY; decrypt_block(tmp, frm->data, blk_idx, fek, uuid); memcpy(out, tmp + offset, size); free(tmp); } else { decrypt_block(out, frm->data, blk_idx, fek, uuid); } } return TEE_SUCCESS; } static TEE_Result tee_rpmb_req_pack(struct rpmb_req *req, struct rpmb_raw_data *rawdata, uint16_t nbr_frms, uint16_t dev_id, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res = TEE_ERROR_GENERIC; int i; struct rpmb_data_frame *datafrm; if (!req || !rawdata || !nbr_frms) return TEE_ERROR_BAD_PARAMETERS; /* * Check write blockcount is not bigger than reliable write * blockcount. */ if ((rawdata->msg_type == RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE) && (nbr_frms > rpmb_ctx->rel_wr_blkcnt)) { DMSG("wr_blkcnt(%d) > rel_wr_blkcnt(%d)", nbr_frms, rpmb_ctx->rel_wr_blkcnt); return TEE_ERROR_GENERIC; } req->cmd = RPMB_CMD_DATA_REQ; req->dev_id = dev_id; /* Allocate memory for construct all data packets and calculate MAC. */ datafrm = calloc(nbr_frms, RPMB_DATA_FRAME_SIZE); if (!datafrm) return TEE_ERROR_OUT_OF_MEMORY; for (i = 0; i < nbr_frms; i++) { u16_to_bytes(rawdata->msg_type, datafrm[i].msg_type); if (rawdata->block_count) u16_to_bytes(*rawdata->block_count, datafrm[i].block_count); if (rawdata->blk_idx) { /* Check the block index is within range. */ if ((*rawdata->blk_idx + nbr_frms) > rpmb_ctx->max_blk_idx) { res = TEE_ERROR_GENERIC; goto func_exit; } u16_to_bytes(*rawdata->blk_idx, datafrm[i].address); } if (rawdata->write_counter) u32_to_bytes(*rawdata->write_counter, datafrm[i].write_counter); if (rawdata->nonce) memcpy(datafrm[i].nonce, rawdata->nonce, RPMB_NONCE_SIZE); if (rawdata->data) { if (fek) encrypt_block(datafrm[i].data, rawdata->data + (i * RPMB_DATA_SIZE), *rawdata->blk_idx + i, fek, uuid); else memcpy(datafrm[i].data, rawdata->data + (i * RPMB_DATA_SIZE), RPMB_DATA_SIZE); } } if (rawdata->key_mac) { if (rawdata->msg_type == RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE) { res = tee_rpmb_mac_calc(rawdata->key_mac, RPMB_KEY_MAC_SIZE, rpmb_ctx->key, RPMB_KEY_MAC_SIZE, datafrm, nbr_frms); if (res != TEE_SUCCESS) goto func_exit; } memcpy(datafrm[nbr_frms - 1].key_mac, rawdata->key_mac, RPMB_KEY_MAC_SIZE); } memcpy(TEE_RPMB_REQ_DATA(req), datafrm, nbr_frms * RPMB_DATA_FRAME_SIZE); #ifdef CFG_RPMB_FS_DEBUG_DATA for (i = 0; i < nbr_frms; i++) { DMSG("Dumping data frame %d:", i); DHEXDUMP((uint8_t *)&datafrm[i] + RPMB_STUFF_DATA_SIZE, 512 - RPMB_STUFF_DATA_SIZE); } #endif res = TEE_SUCCESS; func_exit: free(datafrm); return res; } static TEE_Result data_cpy_mac_calc_1b(struct rpmb_raw_data *rawdata, struct rpmb_data_frame *frm, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res; uint8_t *data; uint16_t idx; if (rawdata->len + rawdata->byte_offset > RPMB_DATA_SIZE) return TEE_ERROR_BAD_PARAMETERS; res = tee_rpmb_mac_calc(rawdata->key_mac, RPMB_KEY_MAC_SIZE, rpmb_ctx->key, RPMB_KEY_MAC_SIZE, frm, 1); if (res != TEE_SUCCESS) return res; data = rawdata->data; bytes_to_u16(frm->address, &idx); res = decrypt(data, frm, rawdata->len, rawdata->byte_offset, idx, fek, uuid); return res; } static TEE_Result tee_rpmb_data_cpy_mac_calc(struct rpmb_data_frame *datafrm, struct rpmb_raw_data *rawdata, uint16_t nbr_frms, struct rpmb_data_frame *lastfrm, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res = TEE_ERROR_GENERIC; int i; void *ctx = NULL; uint16_t offset; uint32_t size; uint8_t *data; uint16_t start_idx; struct rpmb_data_frame localfrm; if (!datafrm || !rawdata || !nbr_frms || !lastfrm) return TEE_ERROR_BAD_PARAMETERS; if (nbr_frms == 1) return data_cpy_mac_calc_1b(rawdata, lastfrm, fek, uuid); /* nbr_frms > 1 */ data = rawdata->data; res = crypto_mac_alloc_ctx(&ctx, TEE_ALG_HMAC_SHA256); if (res) goto func_exit; res = crypto_mac_init(ctx, rpmb_ctx->key, RPMB_KEY_MAC_SIZE); if (res != TEE_SUCCESS) goto func_exit; /* * Note: JEDEC JESD84-B51: "In every packet the address is the start * address of the full access (not address of the individual half a * sector)" */ bytes_to_u16(lastfrm->address, &start_idx); for (i = 0; i < (nbr_frms - 1); i++) { /* * By working on a local copy of the RPMB frame, we ensure that * the data can not be modified after the MAC is computed but * before the payload is decrypted/copied to the output buffer. */ memcpy(&localfrm, &datafrm[i], RPMB_DATA_FRAME_SIZE); res = crypto_mac_update(ctx, localfrm.data, RPMB_MAC_PROTECT_DATA_SIZE); if (res != TEE_SUCCESS) goto func_exit; if (i == 0) { /* First block */ offset = rawdata->byte_offset; size = RPMB_DATA_SIZE - offset; } else { /* Middle blocks */ size = RPMB_DATA_SIZE; offset = 0; } res = decrypt(data, &localfrm, size, offset, start_idx + i, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; data += size; } /* Last block */ size = (rawdata->len + rawdata->byte_offset) % RPMB_DATA_SIZE; if (size == 0) size = RPMB_DATA_SIZE; res = decrypt(data, lastfrm, size, 0, start_idx + nbr_frms - 1, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; /* Update MAC against the last block */ res = crypto_mac_update(ctx, lastfrm->data, RPMB_MAC_PROTECT_DATA_SIZE); if (res != TEE_SUCCESS) goto func_exit; res = crypto_mac_final(ctx, rawdata->key_mac, RPMB_KEY_MAC_SIZE); if (res != TEE_SUCCESS) goto func_exit; res = TEE_SUCCESS; func_exit: crypto_mac_free_ctx(ctx); return res; } static TEE_Result tee_rpmb_resp_unpack_verify(struct rpmb_data_frame *datafrm, struct rpmb_raw_data *rawdata, uint16_t nbr_frms, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res = TEE_ERROR_GENERIC; uint16_t msg_type; uint32_t wr_cnt; uint16_t blk_idx; uint8_t op_result; struct rpmb_data_frame lastfrm; if (!datafrm || !rawdata || !nbr_frms) return TEE_ERROR_BAD_PARAMETERS; #ifdef CFG_RPMB_FS_DEBUG_DATA for (uint32_t i = 0; i < nbr_frms; i++) { DMSG("Dumping data frame %d:", i); DHEXDUMP((uint8_t *)&datafrm[i] + RPMB_STUFF_DATA_SIZE, 512 - RPMB_STUFF_DATA_SIZE); } #endif /* Make sure the last data packet can't be modified once verified */ memcpy(&lastfrm, &datafrm[nbr_frms - 1], RPMB_DATA_FRAME_SIZE); /* Handle operation result and translate to TEEC error code. */ get_op_result_bits(lastfrm.op_result, &op_result); if (op_result == RPMB_RESULT_AUTH_KEY_NOT_PROGRAMMED) return TEE_ERROR_ITEM_NOT_FOUND; if (op_result != RPMB_RESULT_OK) return TEE_ERROR_GENERIC; /* Check the response msg_type. */ bytes_to_u16(lastfrm.msg_type, &msg_type); if (msg_type != rawdata->msg_type) { DMSG("Unexpected msg_type (0x%04x != 0x%04x)", msg_type, rawdata->msg_type); return TEE_ERROR_GENERIC; } if (rawdata->blk_idx) { bytes_to_u16(lastfrm.address, &blk_idx); if (blk_idx != *rawdata->blk_idx) { DMSG("Unexpected block index"); return TEE_ERROR_GENERIC; } } if (rawdata->write_counter) { wr_cnt = *rawdata->write_counter; bytes_to_u32(lastfrm.write_counter, rawdata->write_counter); if (msg_type == RPMB_MSG_TYPE_RESP_AUTH_DATA_WRITE) { /* Verify the write counter is incremented by 1 */ if (*rawdata->write_counter != wr_cnt + 1) { DMSG("Counter mismatched (0x%04x/0x%04x)", *rawdata->write_counter, wr_cnt + 1); return TEE_ERROR_SECURITY; } rpmb_ctx->wr_cnt++; } } if (rawdata->nonce) { if (buf_compare_ct(rawdata->nonce, lastfrm.nonce, RPMB_NONCE_SIZE) != 0) { DMSG("Nonce mismatched"); return TEE_ERROR_SECURITY; } } if (rawdata->key_mac) { if (msg_type == RPMB_MSG_TYPE_RESP_AUTH_DATA_READ) { if (!rawdata->data) return TEE_ERROR_GENERIC; res = tee_rpmb_data_cpy_mac_calc(datafrm, rawdata, nbr_frms, &lastfrm, fek, uuid); if (res != TEE_SUCCESS) return res; } else { /* * There should be only one data frame for * other msg types. */ if (nbr_frms != 1) return TEE_ERROR_GENERIC; res = tee_rpmb_mac_calc(rawdata->key_mac, RPMB_KEY_MAC_SIZE, rpmb_ctx->key, RPMB_KEY_MAC_SIZE, &lastfrm, 1); if (res != TEE_SUCCESS) return res; } #ifndef CFG_RPMB_FS_NO_MAC if (consttime_memcmp(rawdata->key_mac, (datafrm + nbr_frms - 1)->key_mac, RPMB_KEY_MAC_SIZE) != 0) { DMSG("MAC mismatched:"); #ifdef CFG_RPMB_FS_DEBUG_DATA DHEXDUMP((uint8_t *)rawdata->key_mac, 32); #endif return TEE_ERROR_SECURITY; } #endif /* !CFG_RPMB_FS_NO_MAC */ } return TEE_SUCCESS; } static TEE_Result tee_rpmb_get_dev_info(uint16_t dev_id, struct rpmb_dev_info *dev_info) { TEE_Result res = TEE_ERROR_GENERIC; struct tee_rpmb_mem mem; struct rpmb_dev_info *di; struct rpmb_req *req = NULL; uint8_t *resp = NULL; uint32_t req_size; uint32_t resp_size; if (!dev_info) return TEE_ERROR_BAD_PARAMETERS; req_size = sizeof(struct rpmb_req); resp_size = sizeof(struct rpmb_dev_info); res = tee_rpmb_alloc(req_size, resp_size, &mem, (void *)&req, (void *)&resp); if (res != TEE_SUCCESS) goto func_exit; req->cmd = RPMB_CMD_GET_DEV_INFO; req->dev_id = dev_id; di = (struct rpmb_dev_info *)resp; di->ret_code = RPMB_CMD_GET_DEV_INFO_RET_ERROR; res = tee_rpmb_invoke(&mem); if (res != TEE_SUCCESS) goto func_exit; if (di->ret_code != RPMB_CMD_GET_DEV_INFO_RET_OK) { res = TEE_ERROR_GENERIC; goto func_exit; } memcpy((uint8_t *)dev_info, resp, sizeof(struct rpmb_dev_info)); #ifdef CFG_RPMB_FS_DEBUG_DATA DMSG("Dumping dev_info:"); DHEXDUMP((uint8_t *)dev_info, sizeof(struct rpmb_dev_info)); #endif res = TEE_SUCCESS; func_exit: tee_rpmb_free(&mem); return res; } static TEE_Result tee_rpmb_init_read_wr_cnt(uint16_t dev_id, uint32_t *wr_cnt, uint16_t *op_result) { TEE_Result res = TEE_ERROR_GENERIC; struct tee_rpmb_mem mem; uint16_t msg_type; uint8_t nonce[RPMB_NONCE_SIZE]; uint8_t hmac[RPMB_KEY_MAC_SIZE]; struct rpmb_req *req = NULL; struct rpmb_data_frame *resp = NULL; struct rpmb_raw_data rawdata; uint32_t req_size; uint32_t resp_size; if (!wr_cnt) return TEE_ERROR_BAD_PARAMETERS; req_size = sizeof(struct rpmb_req) + RPMB_DATA_FRAME_SIZE; resp_size = RPMB_DATA_FRAME_SIZE; res = tee_rpmb_alloc(req_size, resp_size, &mem, (void *)&req, (void *)&resp); if (res != TEE_SUCCESS) goto func_exit; res = crypto_rng_read(nonce, RPMB_NONCE_SIZE); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_REQ_WRITE_COUNTER_VAL_READ; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.nonce = nonce; res = tee_rpmb_req_pack(req, &rawdata, 1, dev_id, NULL, NULL); if (res != TEE_SUCCESS) goto func_exit; res = tee_rpmb_invoke(&mem); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_RESP_WRITE_COUNTER_VAL_READ; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.op_result = op_result; rawdata.write_counter = wr_cnt; rawdata.nonce = nonce; rawdata.key_mac = hmac; res = tee_rpmb_resp_unpack_verify(resp, &rawdata, 1, NULL, NULL); if (res != TEE_SUCCESS) goto func_exit; res = TEE_SUCCESS; func_exit: tee_rpmb_free(&mem); return res; } static TEE_Result tee_rpmb_verify_key_sync_counter(uint16_t dev_id) { uint16_t op_result = 0; TEE_Result res = TEE_ERROR_GENERIC; res = tee_rpmb_init_read_wr_cnt(dev_id, &rpmb_ctx->wr_cnt, &op_result); if (res == TEE_SUCCESS) { rpmb_ctx->key_verified = true; rpmb_ctx->wr_cnt_synced = true; } else EMSG("Verify key returning 0x%x", res); return res; } #ifdef CFG_RPMB_WRITE_KEY static TEE_Result tee_rpmb_write_key(uint16_t dev_id) { TEE_Result res = TEE_ERROR_GENERIC; struct tee_rpmb_mem mem = { 0 }; uint16_t msg_type; struct rpmb_req *req = NULL; struct rpmb_data_frame *resp = NULL; struct rpmb_raw_data rawdata; uint32_t req_size; uint32_t resp_size; req_size = sizeof(struct rpmb_req) + RPMB_DATA_FRAME_SIZE; resp_size = RPMB_DATA_FRAME_SIZE; res = tee_rpmb_alloc(req_size, resp_size, &mem, (void *)&req, (void *)&resp); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_REQ_AUTH_KEY_PROGRAM; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.key_mac = rpmb_ctx->key; res = tee_rpmb_req_pack(req, &rawdata, 1, dev_id, NULL, NULL); if (res != TEE_SUCCESS) goto func_exit; res = tee_rpmb_invoke(&mem); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_RESP_AUTH_KEY_PROGRAM; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; res = tee_rpmb_resp_unpack_verify(resp, &rawdata, 1, NULL, NULL); if (res != TEE_SUCCESS) goto func_exit; res = TEE_SUCCESS; func_exit: tee_rpmb_free(&mem); return res; } static TEE_Result tee_rpmb_write_and_verify_key(uint16_t dev_id) { TEE_Result res; if (!plat_rpmb_key_is_ready()) { DMSG("RPMB INIT: platform indicates RPMB key is not ready"); return TEE_ERROR_BAD_STATE; } DMSG("RPMB INIT: Writing Key value:"); DHEXDUMP(rpmb_ctx->key, RPMB_KEY_MAC_SIZE); res = tee_rpmb_write_key(dev_id); if (res == TEE_SUCCESS) { DMSG("RPMB INIT: Verifying Key"); res = tee_rpmb_verify_key_sync_counter(dev_id); } return res; } #else static TEE_Result tee_rpmb_write_and_verify_key(uint16_t dev_id __unused) { DMSG("RPMB INIT: CFG_RPMB_WRITE_KEY is not set"); return TEE_ERROR_BAD_STATE; } #endif /* This function must never return TEE_SUCCESS if rpmb_ctx == NULL */ static TEE_Result tee_rpmb_init(uint16_t dev_id) { TEE_Result res = TEE_SUCCESS; struct rpmb_dev_info dev_info; uint32_t nblocks = 0; if (!rpmb_ctx) { rpmb_ctx = calloc(1, sizeof(struct tee_rpmb_ctx)); if (!rpmb_ctx) return TEE_ERROR_OUT_OF_MEMORY; } else if (rpmb_ctx->dev_id != dev_id) { memset(rpmb_ctx, 0x00, sizeof(struct tee_rpmb_ctx)); } rpmb_ctx->dev_id = dev_id; if (!rpmb_ctx->dev_info_synced) { DMSG("RPMB: Syncing device information"); dev_info.rpmb_size_mult = 0; dev_info.rel_wr_sec_c = 0; res = tee_rpmb_get_dev_info(dev_id, &dev_info); if (res != TEE_SUCCESS) goto func_exit; DMSG("RPMB: RPMB size is %d*128 KB", dev_info.rpmb_size_mult); DMSG("RPMB: Reliable Write Sector Count is %d", dev_info.rel_wr_sec_c); if (dev_info.rpmb_size_mult == 0) { res = TEE_ERROR_GENERIC; goto func_exit; } if (MUL_OVERFLOW(dev_info.rpmb_size_mult, RPMB_SIZE_SINGLE / RPMB_DATA_SIZE, &nblocks) || SUB_OVERFLOW(nblocks, 1, &rpmb_ctx->max_blk_idx)) { res = TEE_ERROR_BAD_PARAMETERS; goto func_exit; } memcpy(rpmb_ctx->cid, dev_info.cid, RPMB_EMMC_CID_SIZE); #ifdef RPMB_DRIVER_MULTIPLE_WRITE_FIXED rpmb_ctx->rel_wr_blkcnt = dev_info.rel_wr_sec_c * 2; #else rpmb_ctx->rel_wr_blkcnt = 1; #endif rpmb_ctx->dev_info_synced = true; } if (!rpmb_ctx->key_derived) { DMSG("RPMB INIT: Deriving key"); res = tee_rpmb_key_gen(dev_id, rpmb_ctx->key, RPMB_KEY_MAC_SIZE); if (res != TEE_SUCCESS) { EMSG("RPMB INIT: Deriving key failed with error 0x%x", res); goto func_exit; } rpmb_ctx->key_derived = true; } /* Perform a write counter read to verify if the key is ok. */ if (!rpmb_ctx->wr_cnt_synced || !rpmb_ctx->key_verified) { DMSG("RPMB INIT: Verifying Key"); res = tee_rpmb_verify_key_sync_counter(dev_id); if (res == TEE_ERROR_ITEM_NOT_FOUND && !rpmb_ctx->key_verified) { /* * Need to write the key here and verify it. */ DMSG("RPMB INIT: Auth key not yet written"); res = tee_rpmb_write_and_verify_key(dev_id); } else if (res != TEE_SUCCESS) { EMSG("Verify key failed!"); EMSG("Make sure key here matches device key"); } } func_exit: return res; } /* * Read RPMB data in bytes. * * @dev_id Device ID of the eMMC device. * @addr Byte address of data. * @data Pointer to the data. * @len Size of data in bytes. * @fek Encrypted File Encryption Key or NULL. */ static TEE_Result tee_rpmb_read(uint16_t dev_id, uint32_t addr, uint8_t *data, uint32_t len, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res = TEE_ERROR_GENERIC; struct tee_rpmb_mem mem = { 0 }; uint16_t msg_type; uint8_t nonce[RPMB_NONCE_SIZE]; uint8_t hmac[RPMB_KEY_MAC_SIZE]; struct rpmb_req *req = NULL; struct rpmb_data_frame *resp = NULL; struct rpmb_raw_data rawdata; uint32_t req_size; uint32_t resp_size; uint16_t blk_idx; uint16_t blkcnt; uint8_t byte_offset; if (!data || !len) return TEE_ERROR_BAD_PARAMETERS; blk_idx = addr / RPMB_DATA_SIZE; byte_offset = addr % RPMB_DATA_SIZE; if (len + byte_offset + RPMB_DATA_SIZE < RPMB_DATA_SIZE) { /* Overflow */ return TEE_ERROR_BAD_PARAMETERS; } blkcnt = ROUNDUP(len + byte_offset, RPMB_DATA_SIZE) / RPMB_DATA_SIZE; res = tee_rpmb_init(dev_id); if (res != TEE_SUCCESS) goto func_exit; req_size = sizeof(struct rpmb_req) + RPMB_DATA_FRAME_SIZE; resp_size = RPMB_DATA_FRAME_SIZE * blkcnt; res = tee_rpmb_alloc(req_size, resp_size, &mem, (void *)&req, (void *)&resp); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_REQ_AUTH_DATA_READ; res = crypto_rng_read(nonce, RPMB_NONCE_SIZE); if (res != TEE_SUCCESS) goto func_exit; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.nonce = nonce; rawdata.blk_idx = &blk_idx; res = tee_rpmb_req_pack(req, &rawdata, 1, dev_id, NULL, NULL); if (res != TEE_SUCCESS) goto func_exit; req->block_count = blkcnt; DMSG("Read %u block%s at index %u", blkcnt, ((blkcnt > 1) ? "s" : ""), blk_idx); res = tee_rpmb_invoke(&mem); if (res != TEE_SUCCESS) goto func_exit; msg_type = RPMB_MSG_TYPE_RESP_AUTH_DATA_READ; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.block_count = &blkcnt; rawdata.blk_idx = &blk_idx; rawdata.nonce = nonce; rawdata.key_mac = hmac; rawdata.data = data; rawdata.len = len; rawdata.byte_offset = byte_offset; res = tee_rpmb_resp_unpack_verify(resp, &rawdata, blkcnt, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; res = TEE_SUCCESS; func_exit: tee_rpmb_free(&mem); return res; } static TEE_Result tee_rpmb_write_blk(uint16_t dev_id, uint16_t blk_idx, const uint8_t *data_blks, uint16_t blkcnt, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res; struct tee_rpmb_mem mem; uint16_t msg_type; uint32_t wr_cnt; uint8_t hmac[RPMB_KEY_MAC_SIZE]; struct rpmb_req *req = NULL; struct rpmb_data_frame *resp = NULL; struct rpmb_raw_data rawdata; uint32_t req_size; uint32_t resp_size; uint32_t nbr_writes; uint16_t tmp_blkcnt; uint16_t tmp_blk_idx; uint16_t i; DMSG("Write %u block%s at index %u", blkcnt, ((blkcnt > 1) ? "s" : ""), blk_idx); if (!data_blks || !blkcnt) return TEE_ERROR_BAD_PARAMETERS; res = tee_rpmb_init(dev_id); if (res != TEE_SUCCESS) return res; /* * We need to split data when block count * is bigger than reliable block write count. */ if (blkcnt < rpmb_ctx->rel_wr_blkcnt) req_size = sizeof(struct rpmb_req) + RPMB_DATA_FRAME_SIZE * blkcnt; else req_size = sizeof(struct rpmb_req) + RPMB_DATA_FRAME_SIZE * rpmb_ctx->rel_wr_blkcnt; resp_size = RPMB_DATA_FRAME_SIZE; res = tee_rpmb_alloc(req_size, resp_size, &mem, (void *)&req, (void *)&resp); if (res != TEE_SUCCESS) return res; nbr_writes = blkcnt / rpmb_ctx->rel_wr_blkcnt; if (blkcnt % rpmb_ctx->rel_wr_blkcnt > 0) nbr_writes += 1; tmp_blkcnt = rpmb_ctx->rel_wr_blkcnt; tmp_blk_idx = blk_idx; for (i = 0; i < nbr_writes; i++) { /* * To handle the last write of block count which is * equal or smaller than reliable write block count. */ if (i == nbr_writes - 1) tmp_blkcnt = blkcnt - rpmb_ctx->rel_wr_blkcnt * (nbr_writes - 1); msg_type = RPMB_MSG_TYPE_REQ_AUTH_DATA_WRITE; wr_cnt = rpmb_ctx->wr_cnt; memset(req, 0x00, req_size); memset(resp, 0x00, resp_size); memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.block_count = &tmp_blkcnt; rawdata.blk_idx = &tmp_blk_idx; rawdata.write_counter = &wr_cnt; rawdata.key_mac = hmac; rawdata.data = (uint8_t *)data_blks + i * rpmb_ctx->rel_wr_blkcnt * RPMB_DATA_SIZE; res = tee_rpmb_req_pack(req, &rawdata, tmp_blkcnt, dev_id, fek, uuid); if (res != TEE_SUCCESS) goto out; res = tee_rpmb_invoke(&mem); if (res != TEE_SUCCESS) { /* * To force wr_cnt sync next time, as it might get * out of sync due to inconsistent operation result! */ rpmb_ctx->wr_cnt_synced = false; goto out; } msg_type = RPMB_MSG_TYPE_RESP_AUTH_DATA_WRITE; memset(&rawdata, 0x00, sizeof(struct rpmb_raw_data)); rawdata.msg_type = msg_type; rawdata.block_count = &tmp_blkcnt; rawdata.blk_idx = &tmp_blk_idx; rawdata.write_counter = &wr_cnt; rawdata.key_mac = hmac; res = tee_rpmb_resp_unpack_verify(resp, &rawdata, 1, NULL, NULL); if (res != TEE_SUCCESS) { /* * To force wr_cnt sync next time, as it might get * out of sync due to inconsistent operation result! */ rpmb_ctx->wr_cnt_synced = false; goto out; } tmp_blk_idx += tmp_blkcnt; } out: tee_rpmb_free(&mem); return res; } static bool tee_rpmb_write_is_atomic(uint16_t dev_id __unused, uint32_t addr, uint32_t len) { uint8_t byte_offset = addr % RPMB_DATA_SIZE; uint16_t blkcnt = ROUNDUP(len + byte_offset, RPMB_DATA_SIZE) / RPMB_DATA_SIZE; return (blkcnt <= rpmb_ctx->rel_wr_blkcnt); } /* * Write RPMB data in bytes. * * @dev_id Device ID of the eMMC device. * @addr Byte address of data. * @data Pointer to the data. * @len Size of data in bytes. * @fek Encrypted File Encryption Key or NULL. */ static TEE_Result tee_rpmb_write(uint16_t dev_id, uint32_t addr, const uint8_t *data, uint32_t len, const uint8_t *fek, const TEE_UUID *uuid) { TEE_Result res = TEE_ERROR_GENERIC; uint8_t *data_tmp = NULL; uint16_t blk_idx; uint16_t blkcnt; uint8_t byte_offset; blk_idx = addr / RPMB_DATA_SIZE; byte_offset = addr % RPMB_DATA_SIZE; blkcnt = ROUNDUP(len + byte_offset, RPMB_DATA_SIZE) / RPMB_DATA_SIZE; if (byte_offset == 0 && (len % RPMB_DATA_SIZE) == 0) { res = tee_rpmb_write_blk(dev_id, blk_idx, data, blkcnt, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; } else { data_tmp = calloc(blkcnt, RPMB_DATA_SIZE); if (!data_tmp) { res = TEE_ERROR_OUT_OF_MEMORY; goto func_exit; } /* Read the complete blocks */ res = tee_rpmb_read(dev_id, blk_idx * RPMB_DATA_SIZE, data_tmp, blkcnt * RPMB_DATA_SIZE, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; /* Partial update of the data blocks */ memcpy(data_tmp + byte_offset, data, len); res = tee_rpmb_write_blk(dev_id, blk_idx, data_tmp, blkcnt, fek, uuid); if (res != TEE_SUCCESS) goto func_exit; } res = TEE_SUCCESS; func_exit: free(data_tmp); return res; } /* * Read the RPMB write counter. * * @dev_id Device ID of the eMMC device. * @counter Pointer to the counter. */ static TEE_Result tee_rpmb_get_write_counter(uint16_t dev_id, uint32_t *counter) { TEE_Result res = TEE_SUCCESS; if (!counter) return TEE_ERROR_BAD_PARAMETERS; if (!rpmb_ctx || !rpmb_ctx->wr_cnt_synced) { res = tee_rpmb_init(dev_id); if (res != TEE_SUCCESS) goto func_exit; } *counter = rpmb_ctx->wr_cnt; func_exit: return res; } /* * Read the RPMB max block. * * @dev_id Device ID of the eMMC device. * @counter Pointer to receive the max block. */ static TEE_Result tee_rpmb_get_max_block(uint16_t dev_id, uint32_t *max_block) { TEE_Result res = TEE_SUCCESS; if (!max_block) return TEE_ERROR_BAD_PARAMETERS; if (!rpmb_ctx || !rpmb_ctx->dev_info_synced) { res = tee_rpmb_init(dev_id); if (res != TEE_SUCCESS) goto func_exit; } *max_block = rpmb_ctx->max_blk_idx; func_exit: return res; } /* * End of lower interface to RPMB device */ static TEE_Result get_fat_start_address(uint32_t *addr); #if (TRACE_LEVEL >= TRACE_FLOW) static void dump_fat(void) { TEE_Result res = TEE_ERROR_GENERIC; struct rpmb_fat_entry *fat_entries = NULL; uint32_t fat_address; size_t size; int i; bool last_entry_found = false; res = get_fat_start_address(&fat_address); if (res != TEE_SUCCESS) goto out; size = CFG_RPMB_FS_RD_ENTRIES * sizeof(struct rpmb_fat_entry); fat_entries = malloc(size); if (!fat_entries) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } while (!last_entry_found) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fat_address, (uint8_t *)fat_entries, size, NULL, NULL); if (res != TEE_SUCCESS) goto out; for (i = 0; i < CFG_RPMB_FS_RD_ENTRIES; i++) { FMSG("flags 0x%x, size %d, address 0x%x, filename '%s'", fat_entries[i].flags, fat_entries[i].data_size, fat_entries[i].start_address, fat_entries[i].filename); if ((fat_entries[i].flags & FILE_IS_LAST_ENTRY) != 0) { last_entry_found = true; break; } /* Move to next fat_entry. */ fat_address += sizeof(struct rpmb_fat_entry); } } out: free(fat_entries); } #else static void dump_fat(void) { } #endif #if (TRACE_LEVEL >= TRACE_DEBUG) static void dump_fh(struct rpmb_file_handle *fh) { DMSG("fh->filename=%s", fh->filename); DMSG("fh->rpmb_fat_address=%u", fh->rpmb_fat_address); DMSG("fh->fat_entry.start_address=%u", fh->fat_entry.start_address); DMSG("fh->fat_entry.data_size=%u", fh->fat_entry.data_size); } #else static void dump_fh(struct rpmb_file_handle *fh __unused) { } #endif static struct rpmb_file_handle *alloc_file_handle(struct tee_pobj *po, bool temporary) { struct rpmb_file_handle *fh = NULL; fh = calloc(1, sizeof(struct rpmb_file_handle)); if (!fh) return NULL; if (po) tee_svc_storage_create_filename(fh->filename, sizeof(fh->filename), po, temporary); return fh; } /** * write_fat_entry: Store info in a fat_entry to RPMB. */ static TEE_Result write_fat_entry(struct rpmb_file_handle *fh, bool update_write_counter) { TEE_Result res = TEE_ERROR_GENERIC; /* Protect partition data. */ if (fh->rpmb_fat_address < sizeof(struct rpmb_fs_partition)) { res = TEE_ERROR_ACCESS_CONFLICT; goto out; } if (fh->rpmb_fat_address % sizeof(struct rpmb_fat_entry) != 0) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } if (update_write_counter) { res = tee_rpmb_get_write_counter(CFG_RPMB_FS_DEV_ID, &fh->fat_entry.write_counter); if (res != TEE_SUCCESS) goto out; } res = tee_rpmb_write(CFG_RPMB_FS_DEV_ID, fh->rpmb_fat_address, (uint8_t *)&fh->fat_entry, sizeof(struct rpmb_fat_entry), NULL, NULL); dump_fat(); out: return res; } /** * rpmb_fs_setup: Setup rpmb fs. * Set initial partition and FS values and write to RPMB. * Store frequently used data in RAM. */ static TEE_Result rpmb_fs_setup(void) { TEE_Result res = TEE_ERROR_GENERIC; struct rpmb_fs_partition *partition_data = NULL; struct rpmb_file_handle *fh = NULL; uint32_t max_rpmb_block = 0; if (fs_par) { res = TEE_SUCCESS; goto out; } res = tee_rpmb_get_max_block(CFG_RPMB_FS_DEV_ID, &max_rpmb_block); if (res != TEE_SUCCESS) goto out; partition_data = calloc(1, sizeof(struct rpmb_fs_partition)); if (!partition_data) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, RPMB_STORAGE_START_ADDRESS, (uint8_t *)partition_data, sizeof(struct rpmb_fs_partition), NULL, NULL); if (res != TEE_SUCCESS) goto out; #ifndef CFG_RPMB_RESET_FAT if (partition_data->rpmb_fs_magic == RPMB_FS_MAGIC) { if (partition_data->fs_version == FS_VERSION) { res = TEE_SUCCESS; goto store_fs_par; } else { EMSG("Wrong software is in use."); res = TEE_ERROR_ACCESS_DENIED; goto out; } } #else EMSG("**** Clearing Storage ****"); #endif /* Setup new partition data. */ partition_data->rpmb_fs_magic = RPMB_FS_MAGIC; partition_data->fs_version = FS_VERSION; partition_data->fat_start_address = RPMB_FS_FAT_START_ADDRESS; /* Initial FAT entry with FILE_IS_LAST_ENTRY flag set. */ fh = alloc_file_handle(NULL, false); if (!fh) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } fh->fat_entry.flags = FILE_IS_LAST_ENTRY; fh->rpmb_fat_address = partition_data->fat_start_address; /* Write init FAT entry and partition data to RPMB. */ res = write_fat_entry(fh, true); if (res != TEE_SUCCESS) goto out; res = tee_rpmb_get_write_counter(CFG_RPMB_FS_DEV_ID, &partition_data->write_counter); if (res != TEE_SUCCESS) goto out; res = tee_rpmb_write(CFG_RPMB_FS_DEV_ID, RPMB_STORAGE_START_ADDRESS, (uint8_t *)partition_data, sizeof(struct rpmb_fs_partition), NULL, NULL); #ifndef CFG_RPMB_RESET_FAT store_fs_par: #endif /* Store FAT start address. */ fs_par = calloc(1, sizeof(struct rpmb_fs_parameters)); if (!fs_par) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } fs_par->fat_start_address = partition_data->fat_start_address; fs_par->max_rpmb_address = max_rpmb_block << RPMB_BLOCK_SIZE_SHIFT; dump_fat(); out: free(fh); free(partition_data); return res; } /** * get_fat_start_address: * FAT start_address from fs_par. */ static TEE_Result get_fat_start_address(uint32_t *addr) { if (!fs_par) return TEE_ERROR_NO_DATA; *addr = fs_par->fat_start_address; return TEE_SUCCESS; } /** * read_fat: Read FAT entries * Return matching FAT entry for read, rm rename and stat. * Build up memory pool and return matching entry for write operation. * "Last FAT entry" can be returned during write. */ static TEE_Result read_fat(struct rpmb_file_handle *fh, tee_mm_pool_t *p) { TEE_Result res = TEE_ERROR_GENERIC; tee_mm_entry_t *mm = NULL; struct rpmb_fat_entry *fat_entries = NULL; uint32_t fat_address; size_t size; int i; bool entry_found = false; bool last_entry_found = false; bool expand_fat = false; struct rpmb_file_handle last_fh; DMSG("fat_address %d", fh->rpmb_fat_address); res = rpmb_fs_setup(); if (res != TEE_SUCCESS) goto out; res = get_fat_start_address(&fat_address); if (res != TEE_SUCCESS) goto out; size = CFG_RPMB_FS_RD_ENTRIES * sizeof(struct rpmb_fat_entry); fat_entries = malloc(size); if (!fat_entries) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } /* * The pool is used to represent the current RPMB layout. To find * a slot for the file tee_mm_alloc is called on the pool. Thus * if it is not NULL the entire FAT must be traversed to fill in * the pool. */ while (!last_entry_found && (!entry_found || p)) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fat_address, (uint8_t *)fat_entries, size, NULL, NULL); if (res != TEE_SUCCESS) goto out; for (i = 0; i < CFG_RPMB_FS_RD_ENTRIES; i++) { /* * Look for an entry, matching filenames. (read, rm, * rename and stat.). Only store first filename match. */ if (fh->filename && (strcmp(fh->filename, fat_entries[i].filename) == 0) && (fat_entries[i].flags & FILE_IS_ACTIVE) && (!entry_found)) { entry_found = true; fh->rpmb_fat_address = fat_address; memcpy(&fh->fat_entry, &fat_entries[i], sizeof(struct rpmb_fat_entry)); if (!p) break; } /* Add existing files to memory pool. (write) */ if (p) { if ((fat_entries[i].flags & FILE_IS_ACTIVE) && (fat_entries[i].data_size > 0)) { mm = tee_mm_alloc2 (p, fat_entries[i].start_address, fat_entries[i].data_size); if (!mm) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } } /* Unused FAT entries can be reused (write) */ if (((fat_entries[i].flags & FILE_IS_ACTIVE) == 0) && (fh->rpmb_fat_address == 0)) { fh->rpmb_fat_address = fat_address; memcpy(&fh->fat_entry, &fat_entries[i], sizeof(struct rpmb_fat_entry)); } } if ((fat_entries[i].flags & FILE_IS_LAST_ENTRY) != 0) { last_entry_found = true; /* * If the last entry was reached and was chosen * by the previous check, then the FAT needs to * be expanded. * fh->rpmb_fat_address is the address chosen * to store the files FAT entry and fat_address * is the current FAT entry address being * compared. */ if (p && fh->rpmb_fat_address == fat_address) expand_fat = true; break; } /* Move to next fat_entry. */ fat_address += sizeof(struct rpmb_fat_entry); } } /* * Represent the FAT table in the pool. */ if (p) { /* * Since fat_address is the start of the last entry it needs to * be moved up by an entry. */ fat_address += sizeof(struct rpmb_fat_entry); /* Make room for yet a FAT entry and add to memory pool. */ if (expand_fat) fat_address += sizeof(struct rpmb_fat_entry); mm = tee_mm_alloc2(p, RPMB_STORAGE_START_ADDRESS, fat_address); if (!mm) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } if (expand_fat) { /* * Point fat_address to the beginning of the new * entry. */ fat_address -= sizeof(struct rpmb_fat_entry); memset(&last_fh, 0, sizeof(last_fh)); last_fh.fat_entry.flags = FILE_IS_LAST_ENTRY; last_fh.rpmb_fat_address = fat_address; res = write_fat_entry(&last_fh, true); if (res != TEE_SUCCESS) goto out; } } if (fh->filename && !fh->rpmb_fat_address) res = TEE_ERROR_ITEM_NOT_FOUND; out: free(fat_entries); return res; } static TEE_Result generate_fek(struct rpmb_fat_entry *fe, const TEE_UUID *uuid) { TEE_Result res; again: res = tee_fs_generate_fek(uuid, fe->fek, sizeof(fe->fek)); if (res != TEE_SUCCESS) return res; if (is_zero(fe->fek, sizeof(fe->fek))) goto again; return res; } static TEE_Result rpmb_fs_open_internal(struct rpmb_file_handle *fh, const TEE_UUID *uuid, bool create) { tee_mm_pool_t p; bool pool_result; TEE_Result res = TEE_ERROR_GENERIC; /* We need to do setup in order to make sure fs_par is filled in */ res = rpmb_fs_setup(); if (res != TEE_SUCCESS) goto out; fh->uuid = uuid; if (create) { /* Upper memory allocation must be used for RPMB_FS. */ pool_result = tee_mm_init(&p, RPMB_STORAGE_START_ADDRESS, fs_par->max_rpmb_address, RPMB_BLOCK_SIZE_SHIFT, TEE_MM_POOL_HI_ALLOC); if (!pool_result) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } res = read_fat(fh, &p); tee_mm_final(&p); if (res != TEE_SUCCESS) goto out; } else { res = read_fat(fh, NULL); if (res != TEE_SUCCESS) goto out; } /* * If this is opened with create and the entry found was not active * then this is a new file and the FAT entry must be written */ if (create) { if ((fh->fat_entry.flags & FILE_IS_ACTIVE) == 0) { memset(&fh->fat_entry, 0, sizeof(struct rpmb_fat_entry)); memcpy(fh->fat_entry.filename, fh->filename, strlen(fh->filename)); /* Start address and size are 0 */ fh->fat_entry.flags = FILE_IS_ACTIVE; res = generate_fek(&fh->fat_entry, uuid); if (res != TEE_SUCCESS) goto out; DMSG("GENERATE FEK key: %p", (void *)fh->fat_entry.fek); DHEXDUMP(fh->fat_entry.fek, sizeof(fh->fat_entry.fek)); res = write_fat_entry(fh, true); if (res != TEE_SUCCESS) goto out; } } res = TEE_SUCCESS; out: return res; } static void rpmb_fs_close(struct tee_file_handle **tfh) { struct rpmb_file_handle *fh = (struct rpmb_file_handle *)*tfh; free(fh); *tfh = NULL; } static TEE_Result rpmb_fs_read(struct tee_file_handle *tfh, size_t pos, void *buf, size_t *len) { TEE_Result res; struct rpmb_file_handle *fh = (struct rpmb_file_handle *)tfh; size_t size = *len; if (!size) return TEE_SUCCESS; mutex_lock(&rpmb_mutex); dump_fh(fh); res = read_fat(fh, NULL); if (res != TEE_SUCCESS) goto out; if (pos >= fh->fat_entry.data_size) { *len = 0; goto out; } size = MIN(size, fh->fat_entry.data_size - pos); if (size) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fh->fat_entry.start_address + pos, buf, size, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; } *len = size; out: mutex_unlock(&rpmb_mutex); return res; } static TEE_Result rpmb_fs_write_primitive(struct rpmb_file_handle *fh, size_t pos, const void *buf, size_t size) { TEE_Result res; tee_mm_pool_t p; bool pool_result = false; tee_mm_entry_t *mm; size_t end; size_t newsize; uint8_t *newbuf = NULL; uintptr_t newaddr; uint32_t start_addr; if (!size) return TEE_SUCCESS; if (!fs_par) { res = TEE_ERROR_GENERIC; goto out; } dump_fh(fh); /* Upper memory allocation must be used for RPMB_FS. */ pool_result = tee_mm_init(&p, RPMB_STORAGE_START_ADDRESS, fs_par->max_rpmb_address, RPMB_BLOCK_SIZE_SHIFT, TEE_MM_POOL_HI_ALLOC); if (!pool_result) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } res = read_fat(fh, &p); if (res != TEE_SUCCESS) goto out; if (fh->fat_entry.flags & FILE_IS_LAST_ENTRY) panic("invalid last entry flag"); if (ADD_OVERFLOW(pos, size, &end)) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } if (ADD_OVERFLOW(fh->fat_entry.start_address, pos, &start_addr)) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } if (end <= fh->fat_entry.data_size && tee_rpmb_write_is_atomic(CFG_RPMB_FS_DEV_ID, start_addr, size)) { DMSG("Updating data in-place"); res = tee_rpmb_write(CFG_RPMB_FS_DEV_ID, start_addr, buf, size, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; } else { /* * File must be extended, or update cannot be atomic: allocate, * read, update, write. */ DMSG("Need to re-allocate"); newsize = MAX(end, fh->fat_entry.data_size); mm = tee_mm_alloc(&p, newsize); newbuf = calloc(1, newsize); if (!mm || !newbuf) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } if (fh->fat_entry.data_size) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fh->fat_entry.start_address, newbuf, fh->fat_entry.data_size, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; } memcpy(newbuf + pos, buf, size); newaddr = tee_mm_get_smem(mm); res = tee_rpmb_write(CFG_RPMB_FS_DEV_ID, newaddr, newbuf, newsize, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; fh->fat_entry.data_size = newsize; fh->fat_entry.start_address = newaddr; res = write_fat_entry(fh, true); if (res != TEE_SUCCESS) goto out; } out: if (pool_result) tee_mm_final(&p); if (newbuf) free(newbuf); return res; } static TEE_Result rpmb_fs_write(struct tee_file_handle *tfh, size_t pos, const void *buf, size_t size) { TEE_Result res; mutex_lock(&rpmb_mutex); res = rpmb_fs_write_primitive((struct rpmb_file_handle *)tfh, pos, buf, size); mutex_unlock(&rpmb_mutex); return res; } static TEE_Result rpmb_fs_remove_internal(struct rpmb_file_handle *fh) { TEE_Result res; res = read_fat(fh, NULL); if (res) return res; /* Clear this file entry. */ memset(&fh->fat_entry, 0, sizeof(struct rpmb_fat_entry)); return write_fat_entry(fh, false); } static TEE_Result rpmb_fs_remove(struct tee_pobj *po) { TEE_Result res; struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); if (!fh) return TEE_ERROR_OUT_OF_MEMORY; mutex_lock(&rpmb_mutex); res = rpmb_fs_remove_internal(fh); mutex_unlock(&rpmb_mutex); free(fh); return res; } static TEE_Result rpmb_fs_rename_internal(struct tee_pobj *old, struct tee_pobj *new, bool overwrite) { TEE_Result res = TEE_ERROR_GENERIC; struct rpmb_file_handle *fh_old = NULL; struct rpmb_file_handle *fh_new = NULL; if (!old) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } if (new) fh_old = alloc_file_handle(old, old->temporary); else fh_old = alloc_file_handle(old, true); if (!fh_old) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } if (new) fh_new = alloc_file_handle(new, new->temporary); else fh_new = alloc_file_handle(old, false); if (!fh_new) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } res = read_fat(fh_old, NULL); if (res != TEE_SUCCESS) goto out; res = read_fat(fh_new, NULL); if (res == TEE_SUCCESS) { if (!overwrite) { res = TEE_ERROR_ACCESS_CONFLICT; goto out; } /* Clear this file entry. */ memset(&fh_new->fat_entry, 0, sizeof(struct rpmb_fat_entry)); res = write_fat_entry(fh_new, false); if (res != TEE_SUCCESS) goto out; } memset(fh_old->fat_entry.filename, 0, TEE_RPMB_FS_FILENAME_LENGTH); memcpy(fh_old->fat_entry.filename, fh_new->filename, strlen(fh_new->filename)); res = write_fat_entry(fh_old, false); out: free(fh_old); free(fh_new); return res; } static TEE_Result rpmb_fs_rename(struct tee_pobj *old, struct tee_pobj *new, bool overwrite) { TEE_Result res; mutex_lock(&rpmb_mutex); res = rpmb_fs_rename_internal(old, new, overwrite); mutex_unlock(&rpmb_mutex); return res; } static TEE_Result rpmb_fs_truncate(struct tee_file_handle *tfh, size_t length) { struct rpmb_file_handle *fh = (struct rpmb_file_handle *)tfh; tee_mm_pool_t p; bool pool_result = false; tee_mm_entry_t *mm; uint32_t newsize; uint8_t *newbuf = NULL; uintptr_t newaddr; TEE_Result res = TEE_ERROR_GENERIC; mutex_lock(&rpmb_mutex); if (length > INT32_MAX) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } newsize = length; res = read_fat(fh, NULL); if (res != TEE_SUCCESS) goto out; if (newsize > fh->fat_entry.data_size) { /* Extend file */ pool_result = tee_mm_init(&p, RPMB_STORAGE_START_ADDRESS, fs_par->max_rpmb_address, RPMB_BLOCK_SIZE_SHIFT, TEE_MM_POOL_HI_ALLOC); if (!pool_result) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } res = read_fat(fh, &p); if (res != TEE_SUCCESS) goto out; mm = tee_mm_alloc(&p, newsize); newbuf = calloc(1, newsize); if (!mm || !newbuf) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } if (fh->fat_entry.data_size) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fh->fat_entry.start_address, newbuf, fh->fat_entry.data_size, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; } newaddr = tee_mm_get_smem(mm); res = tee_rpmb_write(CFG_RPMB_FS_DEV_ID, newaddr, newbuf, newsize, fh->fat_entry.fek, fh->uuid); if (res != TEE_SUCCESS) goto out; } else { /* Don't change file location */ newaddr = fh->fat_entry.start_address; } /* fh->pos is unchanged */ fh->fat_entry.data_size = newsize; fh->fat_entry.start_address = newaddr; res = write_fat_entry(fh, true); out: mutex_unlock(&rpmb_mutex); if (pool_result) tee_mm_final(&p); if (newbuf) free(newbuf); return res; } static void rpmb_fs_dir_free(struct tee_fs_dir *dir) { struct tee_rpmb_fs_dirent *e; if (!dir) return; free(dir->current); while ((e = SIMPLEQ_FIRST(&dir->next))) { SIMPLEQ_REMOVE_HEAD(&dir->next, link); free(e); } } static TEE_Result rpmb_fs_dir_populate(const char *path, struct tee_fs_dir *dir) { struct tee_rpmb_fs_dirent *current = NULL; struct rpmb_fat_entry *fat_entries = NULL; uint32_t fat_address; uint32_t filelen; char *filename; int i; bool last_entry_found = false; bool matched; struct tee_rpmb_fs_dirent *next = NULL; uint32_t pathlen; TEE_Result res = TEE_ERROR_GENERIC; uint32_t size; char temp; mutex_lock(&rpmb_mutex); res = rpmb_fs_setup(); if (res != TEE_SUCCESS) goto out; res = get_fat_start_address(&fat_address); if (res != TEE_SUCCESS) goto out; size = CFG_RPMB_FS_RD_ENTRIES * sizeof(struct rpmb_fat_entry); fat_entries = malloc(size); if (!fat_entries) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } pathlen = strlen(path); while (!last_entry_found) { res = tee_rpmb_read(CFG_RPMB_FS_DEV_ID, fat_address, (uint8_t *)fat_entries, size, NULL, NULL); if (res != TEE_SUCCESS) goto out; for (i = 0; i < CFG_RPMB_FS_RD_ENTRIES; i++) { filename = fat_entries[i].filename; if (fat_entries[i].flags & FILE_IS_ACTIVE) { matched = false; filelen = strlen(filename); if (filelen > pathlen) { temp = filename[pathlen]; filename[pathlen] = '\0'; if (strcmp(filename, path) == 0) matched = true; filename[pathlen] = temp; } if (matched) { next = malloc(sizeof(*next)); if (!next) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } next->entry.oidlen = tee_hs2b( (uint8_t *)&filename[pathlen], next->entry.oid, filelen - pathlen, sizeof(next->entry.oid)); if (next->entry.oidlen) { SIMPLEQ_INSERT_TAIL(&dir->next, next, link); current = next; } else { free(next); next = NULL; } } } if (fat_entries[i].flags & FILE_IS_LAST_ENTRY) { last_entry_found = true; break; } /* Move to next fat_entry. */ fat_address += sizeof(struct rpmb_fat_entry); } } if (current) res = TEE_SUCCESS; else res = TEE_ERROR_ITEM_NOT_FOUND; /* No directories were found. */ out: mutex_unlock(&rpmb_mutex); if (res != TEE_SUCCESS) rpmb_fs_dir_free(dir); if (fat_entries) free(fat_entries); return res; } static TEE_Result rpmb_fs_opendir(const TEE_UUID *uuid, struct tee_fs_dir **dir) { uint32_t len; char path_local[TEE_RPMB_FS_FILENAME_LENGTH]; TEE_Result res = TEE_ERROR_GENERIC; struct tee_fs_dir *rpmb_dir = NULL; if (!uuid || !dir) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } memset(path_local, 0, sizeof(path_local)); if (tee_svc_storage_create_dirname(path_local, sizeof(path_local) - 1, uuid) != TEE_SUCCESS) { res = TEE_ERROR_BAD_PARAMETERS; goto out; } len = strlen(path_local); /* Add a slash to correctly match the full directory name. */ if (path_local[len - 1] != '/') path_local[len] = '/'; rpmb_dir = calloc(1, sizeof(*rpmb_dir)); if (!rpmb_dir) { res = TEE_ERROR_OUT_OF_MEMORY; goto out; } SIMPLEQ_INIT(&rpmb_dir->next); res = rpmb_fs_dir_populate(path_local, rpmb_dir); if (res != TEE_SUCCESS) { free(rpmb_dir); rpmb_dir = NULL; goto out; } *dir = rpmb_dir; out: return res; } static TEE_Result rpmb_fs_readdir(struct tee_fs_dir *dir, struct tee_fs_dirent **ent) { if (!dir) return TEE_ERROR_GENERIC; free(dir->current); dir->current = SIMPLEQ_FIRST(&dir->next); if (!dir->current) return TEE_ERROR_ITEM_NOT_FOUND; SIMPLEQ_REMOVE_HEAD(&dir->next, link); *ent = &dir->current->entry; return TEE_SUCCESS; } static void rpmb_fs_closedir(struct tee_fs_dir *dir) { if (dir) { rpmb_fs_dir_free(dir); free(dir); } } static TEE_Result rpmb_fs_open(struct tee_pobj *po, size_t *size, struct tee_file_handle **ret_fh) { TEE_Result res; struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); if (!fh) return TEE_ERROR_OUT_OF_MEMORY; mutex_lock(&rpmb_mutex); res = rpmb_fs_open_internal(fh, &po->uuid, false); if (!res && size) *size = fh->fat_entry.data_size; mutex_unlock(&rpmb_mutex); if (res) free(fh); else *ret_fh = (struct tee_file_handle *)fh; return res; } static TEE_Result rpmb_fs_create(struct tee_pobj *po, bool overwrite, const void *head, size_t head_size, const void *attr, size_t attr_size, const void *data, size_t data_size, struct tee_file_handle **ret_fh) { TEE_Result res; size_t pos = 0; struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); if (!fh) return TEE_ERROR_OUT_OF_MEMORY; mutex_lock(&rpmb_mutex); res = rpmb_fs_open_internal(fh, &po->uuid, true); if (res) goto out; if (head && head_size) { res = rpmb_fs_write_primitive(fh, pos, head, head_size); if (res) goto out; pos += head_size; } if (attr && attr_size) { res = rpmb_fs_write_primitive(fh, pos, attr, attr_size); if (res) goto out; pos += attr_size; } if (data && data_size) { res = rpmb_fs_write_primitive(fh, pos, data, data_size); if (res) goto out; } if (po->temporary) { /* * If it's a temporary filename (which it normally is) * rename into the final filename now that the file is * fully initialized. */ po->temporary = false; res = rpmb_fs_rename_internal(po, NULL, overwrite); if (res) { po->temporary = true; goto out; } /* Update file handle after rename. */ tee_svc_storage_create_filename(fh->filename, sizeof(fh->filename), po, false); } out: if (res) { rpmb_fs_remove_internal(fh); free(fh); } else { *ret_fh = (struct tee_file_handle *)fh; } mutex_unlock(&rpmb_mutex); return res; } const struct tee_file_operations rpmb_fs_ops = { .open = rpmb_fs_open, .create = rpmb_fs_create, .close = rpmb_fs_close, .read = rpmb_fs_read, .write = rpmb_fs_write, .truncate = rpmb_fs_truncate, .rename = rpmb_fs_rename, .remove = rpmb_fs_remove, .opendir = rpmb_fs_opendir, .closedir = rpmb_fs_closedir, .readdir = rpmb_fs_readdir, }; TEE_Result tee_rpmb_fs_raw_open(const char *fname, bool create, struct tee_file_handle **ret_fh) { TEE_Result res; struct rpmb_file_handle *fh = calloc(1, sizeof(*fh)); static const TEE_UUID uuid = { 0 }; if (!fh) return TEE_ERROR_OUT_OF_MEMORY; snprintf(fh->filename, sizeof(fh->filename), "/%s", fname); mutex_lock(&rpmb_mutex); res = rpmb_fs_open_internal(fh, &uuid, create); mutex_unlock(&rpmb_mutex); if (res) { if (create) rpmb_fs_remove_internal(fh); free(fh); } else { *ret_fh = (struct tee_file_handle *)fh; } return res; } bool __weak plat_rpmb_key_is_ready(void) { return true; }