blob: 906b23710b788e1744cce916800cec4976f30358 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2014, STMicroelectronics International N.V.
*/
#include <assert.h>
#include <crypto/crypto.h>
#include <kernel/huk_subkey.h>
#include <kernel/misc.h>
#include <kernel/msg_param.h>
#include <kernel/mutex.h>
#include <kernel/panic.h>
#include <kernel/tee_common.h>
#include <kernel/tee_common_otp.h>
#include <kernel/tee_misc.h>
#include <kernel/thread.h>
#include <mm/core_memprot.h>
#include <mm/mobj.h>
#include <mm/tee_mm.h>
#include <optee_rpc_cmd.h>
#include <stdlib.h>
#include <string_ext.h>
#include <string.h>
#include <sys/queue.h>
#include <tee/tee_fs.h>
#include <tee/tee_fs_key_manager.h>
#include <tee/tee_pobj.h>
#include <tee/tee_svc_storage.h>
#include <trace.h>
#include <util.h>
#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 N_ENTRIES 8
#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;
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);
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 = N_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 < N_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);
}
#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 = N_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 < N_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 = N_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 < N_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;
}