From f9a43781767007462965b21f3f518c4cfc0744c7 Mon Sep 17 00:00:00 2001 From: "r.tyminski" Date: Mon, 29 May 2017 11:42:10 +0200 Subject: Initial commit with upstream sources Change-Id: Ie9460111f21fc955102fd8732a0173b2d0499a4a --- core/tee/tee_sql_fs.c | 610 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 core/tee/tee_sql_fs.c (limited to 'core/tee/tee_sql_fs.c') diff --git a/core/tee/tee_sql_fs.c b/core/tee/tee_sql_fs.c new file mode 100644 index 0000000..e38e1bc --- /dev/null +++ b/core/tee/tee_sql_fs.c @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2016, Linaro Limited + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file implements the tee_file_operations structure for a secure + * filesystem based on an SQLite database in normal world. + * The atomicity of each operation is ensured by using SQL transactions. + * The main purpose of the code below is to perform block encryption and + * authentication of the file data, and properly handle seeking through the + * file. One file (in the sense of struct tee_file_operations) maps to one + * file in the SQL filesystem, and has the following structure: + * + * [ File meta-data ][ Block #0 ][Block #1]... + * [meta_header|sql_fs_file_meta][block_header|user data][ ]... + * + * meta_header and block_header are defined in tee_fs_key_manager.h. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Block size for encryption */ +#define BLOCK_SHIFT 12 +#define BLOCK_SIZE (1 << BLOCK_SHIFT) + +struct sql_fs_file_meta { + size_t length; +}; + +/* File descriptor */ +struct sql_fs_fd { + struct sql_fs_file_meta meta; + uint8_t encrypted_fek[TEE_FS_KM_FEK_SIZE]; + tee_fs_off_t pos; + int fd; /* returned by normal world */ + int flags; /* open flags */ +}; + +struct tee_fs_dir { + int nw_dir; + struct tee_fs_dirent d; +}; + +static struct mutex sql_fs_mutex = MUTEX_INITIALIZER; + +/* + * Interface with tee-supplicant + */ + +static TEE_Result sql_fs_begin_transaction_rpc(void) +{ + return tee_fs_rpc_begin_transaction(OPTEE_MSG_RPC_CMD_SQL_FS); +} + +static TEE_Result sql_fs_end_transaction_rpc(bool rollback) +{ + return tee_fs_rpc_end_transaction(OPTEE_MSG_RPC_CMD_SQL_FS, + rollback); +} + +static TEE_Result sql_fs_opendir_rpc(const char *name, struct tee_fs_dir **d) +{ + return tee_fs_rpc_opendir(OPTEE_MSG_RPC_CMD_SQL_FS, name, d); +} + +static TEE_Result sql_fs_readdir_rpc(struct tee_fs_dir *d, + struct tee_fs_dirent **ent) +{ + return tee_fs_rpc_readdir(OPTEE_MSG_RPC_CMD_SQL_FS, d, ent); +} + +static TEE_Result sql_fs_remove_rpc(const char *file) +{ + return tee_fs_rpc_remove(OPTEE_MSG_RPC_CMD_SQL_FS, file); +} + +static TEE_Result sql_fs_rename_rpc(const char *old, const char *nw, + bool overwrite) +{ + return tee_fs_rpc_rename(OPTEE_MSG_RPC_CMD_SQL_FS, old, nw, overwrite); +} + +static void sql_fs_closedir_rpc(struct tee_fs_dir *d) +{ + if (d) + tee_fs_rpc_closedir(OPTEE_MSG_RPC_CMD_SQL_FS, d); +} + +/* + * End of interface with tee-supplicant + */ + +static size_t meta_size(void) +{ + return tee_fs_get_header_size(META_FILE) + + sizeof(struct sql_fs_file_meta); +} + +static size_t block_header_size(void) +{ + return tee_fs_get_header_size(BLOCK_FILE); +} + +static size_t block_size_raw(void) +{ + return block_header_size() + BLOCK_SIZE; +} + +/* Return the block number from a position in the user data */ +static ssize_t block_num(tee_fs_off_t pos) +{ + return pos / BLOCK_SIZE; +} + +/* Return the position of a block in the DB file */ +static ssize_t block_pos_raw(size_t block_num) +{ + return meta_size() + block_num * block_size_raw(); +} + +static TEE_Result write_meta(struct sql_fs_fd *fdp) +{ + TEE_Result res; + size_t ct_size = meta_size(); + void *ct; + struct tee_fs_rpc_operation op; + + res = tee_fs_rpc_write_init(&op, OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd, 0, + ct_size, &ct); + if (res != TEE_SUCCESS) + return res; + + + res = tee_fs_encrypt_file(META_FILE, + (const uint8_t *)&fdp->meta, + sizeof(fdp->meta), ct, &ct_size, + fdp->encrypted_fek); + if (res != TEE_SUCCESS) + return res; + + return tee_fs_rpc_write_final(&op); +} + +static TEE_Result create_meta(struct sql_fs_fd *fdp, const char *fname) +{ + TEE_Result res; + + memset(&fdp->meta, 0, sizeof(fdp->meta)); + + res = tee_fs_generate_fek(fdp->encrypted_fek, TEE_FS_KM_FEK_SIZE); + if (res != TEE_SUCCESS) + return res; + + res = tee_fs_rpc_create(OPTEE_MSG_RPC_CMD_SQL_FS, fname, &fdp->fd); + if (res != TEE_SUCCESS) + return res; + + return write_meta(fdp); +} + +static TEE_Result read_meta(struct sql_fs_fd *fdp, const char *fname) +{ + TEE_Result res; + size_t msize = meta_size(); + size_t out_size = sizeof(fdp->meta); + void *meta; + size_t bytes; + struct tee_fs_rpc_operation op; + + res = tee_fs_rpc_open(OPTEE_MSG_RPC_CMD_SQL_FS, fname, &fdp->fd); + if (res != TEE_SUCCESS) + return res; + + res = tee_fs_rpc_read_init(&op, OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd, 0, + msize, &meta); + if (res != TEE_SUCCESS) + return res; + + res = tee_fs_rpc_read_final(&op, &bytes); + if (res != TEE_SUCCESS) + return res; + + return tee_fs_decrypt_file(META_FILE, meta, msize, + (uint8_t *)&fdp->meta, &out_size, + fdp->encrypted_fek); +} + +/* + * Read one block of user data. + * Returns: + * < 0: read error + * 0: block does not exist (reading past last block) + * > 0: success + */ +static TEE_Result read_block(struct sql_fs_fd *fdp, size_t bnum, uint8_t *data) +{ + TEE_Result res; + size_t ct_size = block_size_raw(); + size_t out_size = BLOCK_SIZE; + ssize_t pos = block_pos_raw(bnum); + size_t bytes; + void *ct; + struct tee_fs_rpc_operation op; + + res = tee_fs_rpc_read_init(&op, OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd, pos, + ct_size, &ct); + if (res != TEE_SUCCESS) + return res; + res = tee_fs_rpc_read_final(&op, &bytes); + if (res != TEE_SUCCESS) + return res; + if (!bytes) + return TEE_SUCCESS; /* Block does not exist */ + + return tee_fs_decrypt_file(BLOCK_FILE, ct, bytes, data, + &out_size, fdp->encrypted_fek); +} + +/* Write one block of user data */ +static TEE_Result write_block(struct sql_fs_fd *fdp, size_t bnum, uint8_t *data) +{ + TEE_Result res; + size_t ct_size = block_size_raw(); + ssize_t pos = block_pos_raw(bnum); + void *ct; + struct tee_fs_rpc_operation op; + + res = tee_fs_rpc_write_init(&op, OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd, pos, + ct_size, &ct); + if (res != TEE_SUCCESS) + return res; + + res = tee_fs_encrypt_file(BLOCK_FILE, data, BLOCK_SIZE, ct, + &ct_size, fdp->encrypted_fek); + if (res != TEE_SUCCESS) + return res; + + return tee_fs_rpc_write_final(&op); +} + +/* + * Partial write (< BLOCK_SIZE) into a block: read/update/write + * To save memory, passing data == NULL is equivalent to passing a buffer + * filled with zeroes. + */ +static TEE_Result write_block_partial(struct sql_fs_fd *fdp, size_t bnum, + const uint8_t *data, size_t len, + size_t offset) +{ + TEE_Result res; + size_t buf_size = BLOCK_SIZE; + uint8_t *buf = NULL; + + if ((offset >= buf_size) || (offset + len > buf_size)) + return TEE_ERROR_BAD_PARAMETERS; + + buf = malloc(buf_size); + if (!buf) + return TEE_ERROR_OUT_OF_MEMORY; + + res = read_block(fdp, bnum, buf); + if (res != TEE_SUCCESS) + goto exit; + + if (data) + memcpy(buf + offset, data, len); + else + memset(buf + offset, 0, len); + + res = write_block(fdp, bnum, buf); +exit: + free(buf); + return res; +} + +static TEE_Result sql_fs_ftruncate_internal(struct sql_fs_fd *fdp, + tee_fs_off_t new_length) +{ + TEE_Result res; + tee_fs_off_t old_length; + + old_length = (tee_fs_off_t)fdp->meta.length; + + if (new_length == old_length) + return TEE_SUCCESS; + + sql_fs_begin_transaction_rpc(); + + if (new_length < old_length) { + /* Trim unused blocks */ + int old_last_block = block_num(old_length); + int last_block = block_num(new_length); + tee_fs_off_t off; + + if (last_block < old_last_block) { + off = block_pos_raw(last_block); + res = tee_fs_rpc_truncate(OPTEE_MSG_RPC_CMD_SQL_FS, + fdp->fd, off); + if (res != TEE_SUCCESS) + goto exit; + } + } else { + /* Extend file with zeroes */ + tee_fs_off_t off = old_length % BLOCK_SIZE; + size_t bnum = block_num(old_length); + size_t end_bnum = block_num(new_length); + + while (bnum <= end_bnum) { + size_t len = (size_t)BLOCK_SIZE - (size_t)off; + + res = write_block_partial(fdp, bnum, NULL, len, off); + if (res != TEE_SUCCESS) + goto exit; + off = 0; + bnum++; + } + } + + fdp->meta.length = new_length; + res = write_meta(fdp); +exit: + sql_fs_end_transaction_rpc(res != TEE_SUCCESS); + return res; +} + +static TEE_Result sql_fs_seek(struct tee_file_handle *fh, int32_t offset, + TEE_Whence whence, int32_t *new_offs) +{ + TEE_Result res; + struct sql_fs_fd *fdp = (struct sql_fs_fd *)fh; + tee_fs_off_t pos; + + mutex_lock(&sql_fs_mutex); + + switch (whence) { + case TEE_DATA_SEEK_SET: + pos = offset; + break; + + case TEE_DATA_SEEK_CUR: + pos = fdp->pos + offset; + break; + + case TEE_DATA_SEEK_END: + pos = fdp->meta.length + offset; + break; + + default: + res = TEE_ERROR_BAD_PARAMETERS; + goto exit_ret; + } + + if (pos > TEE_DATA_MAX_POSITION) { + EMSG("Position is beyond TEE_DATA_MAX_POSITION"); + res = TEE_ERROR_BAD_PARAMETERS; + goto exit_ret; + } + + if (pos < 0) + pos = 0; + + fdp->pos = pos; + if (new_offs) + *new_offs = pos; + res = TEE_SUCCESS; +exit_ret: + mutex_unlock(&sql_fs_mutex); + return res; +} + +static void sql_fs_close(struct tee_file_handle **fh) +{ + struct sql_fs_fd *fdp = (struct sql_fs_fd *)*fh; + + if (fdp) { + tee_fs_rpc_close(OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd); + free(fdp); + *fh = NULL; + } +} + +static TEE_Result open_internal(const char *file, bool create, + struct tee_file_handle **fh) +{ + TEE_Result res; + struct sql_fs_fd *fdp; + bool created = false; + + fdp = calloc(1, sizeof(*fdp)); + if (!fdp) + return TEE_ERROR_OUT_OF_MEMORY; + fdp->fd = -1; + + mutex_lock(&sql_fs_mutex); + + if (create) + res = create_meta(fdp, file); + else + res = read_meta(fdp, file); + + if (res == TEE_SUCCESS) { + *fh = (struct tee_file_handle *)fdp; + } else { + if (fdp && fdp->fd != -1) + tee_fs_rpc_close(OPTEE_MSG_RPC_CMD_SQL_FS, fdp->fd); + if (created) + tee_fs_rpc_remove(OPTEE_MSG_RPC_CMD_SQL_FS, file); + free(fdp); + } + mutex_unlock(&sql_fs_mutex); + return res; +} + +static TEE_Result sql_fs_open(const char *file, struct tee_file_handle **fh) +{ + return open_internal(file, false, fh); +} + +static TEE_Result sql_fs_create(const char *file, struct tee_file_handle **fh) +{ + return open_internal(file, true, fh); +} + + +static TEE_Result sql_fs_read(struct tee_file_handle *fh, void *buf, + size_t *len) +{ + TEE_Result res; + struct sql_fs_fd *fdp = (struct sql_fs_fd *)fh; + size_t remain_bytes = *len; + uint8_t *data_ptr = buf; + uint8_t *block = NULL; + int start_block_num; + int end_block_num; + + mutex_lock(&sql_fs_mutex); + + if ((fdp->pos + remain_bytes) < remain_bytes || + fdp->pos > (tee_fs_off_t)fdp->meta.length) + remain_bytes = 0; + else if (fdp->pos + remain_bytes > fdp->meta.length) + remain_bytes = fdp->meta.length - fdp->pos; + + *len = remain_bytes; + + if (!remain_bytes) { + res = TEE_SUCCESS; + goto exit; + } + + start_block_num = block_num(fdp->pos); + end_block_num = block_num(fdp->pos + remain_bytes - 1); + + block = malloc(BLOCK_SIZE); + if (!block) { + res = TEE_ERROR_OUT_OF_MEMORY; + goto exit; + } + + while (start_block_num <= end_block_num) { + tee_fs_off_t offset = fdp->pos % BLOCK_SIZE; + size_t size_to_read = MIN(remain_bytes, (size_t)BLOCK_SIZE); + + if (size_to_read + offset > BLOCK_SIZE) + size_to_read = BLOCK_SIZE - offset; + + /* + * REVISIT: implement read_block_partial() since we have + * write_block_partial()? + */ + res = read_block(fdp, start_block_num, block); + if (res != TEE_SUCCESS) + goto exit; + + memcpy(data_ptr, block + offset, size_to_read); + + data_ptr += size_to_read; + remain_bytes -= size_to_read; + fdp->pos += size_to_read; + + start_block_num++; + } + res = TEE_SUCCESS; +exit: + free(block); + mutex_unlock(&sql_fs_mutex); + return res; +} + +static TEE_Result sql_fs_write(struct tee_file_handle *fh, const void *buf, + size_t len) +{ + TEE_Result res; + struct sql_fs_fd *fdp = (struct sql_fs_fd *)fh; + size_t remain_bytes = len; + const uint8_t *data_ptr = buf; + int start_block_num; + int end_block_num; + + if (!len) + return TEE_SUCCESS; + + mutex_lock(&sql_fs_mutex); + + sql_fs_begin_transaction_rpc(); + + if (fdp->meta.length < (size_t)fdp->pos) { + /* Fill hole */ + res = sql_fs_ftruncate_internal(fdp, fdp->pos); + if (res != TEE_SUCCESS) + goto exit; + } + + start_block_num = block_num(fdp->pos); + end_block_num = block_num(fdp->pos + len - 1); + + while (start_block_num <= end_block_num) { + tee_fs_off_t offset = fdp->pos % BLOCK_SIZE; + size_t size_to_write = MIN(remain_bytes, (size_t)BLOCK_SIZE); + + if (size_to_write + offset > BLOCK_SIZE) + size_to_write = BLOCK_SIZE - offset; + + res = write_block_partial(fdp, start_block_num, data_ptr, + size_to_write, offset); + if (res != TEE_SUCCESS) + goto exit; + + data_ptr += size_to_write; + remain_bytes -= size_to_write; + fdp->pos += size_to_write; + + start_block_num++; + } + + if (fdp->meta.length < (size_t)fdp->pos) { + fdp->meta.length = fdp->pos; + res = write_meta(fdp); + } +exit: + sql_fs_end_transaction_rpc(res != TEE_SUCCESS); + mutex_unlock(&sql_fs_mutex); + return res; +} + +static TEE_Result sql_fs_truncate(struct tee_file_handle *fh, size_t len) +{ + TEE_Result res; + struct sql_fs_fd *fdp = (struct sql_fs_fd *)fh; + + mutex_lock(&sql_fs_mutex); + res = sql_fs_ftruncate_internal(fdp, len); + mutex_unlock(&sql_fs_mutex); + + return res; +} + +const struct tee_file_operations sql_fs_ops = { + .open = sql_fs_open, + .create = sql_fs_create, + .close = sql_fs_close, + .read = sql_fs_read, + .write = sql_fs_write, + .seek = sql_fs_seek, + .truncate = sql_fs_truncate, + + .opendir = sql_fs_opendir_rpc, + .closedir = sql_fs_closedir_rpc, + .readdir = sql_fs_readdir_rpc, + .rename = sql_fs_rename_rpc, + .remove = sql_fs_remove_rpc, +}; -- cgit v1.2.3