From 361fb3e3471be20334c668d421bb4f8ab685afc7 Mon Sep 17 00:00:00 2001 From: Jens Wiklander Date: Thu, 6 Oct 2016 16:56:57 +0200 Subject: [PATCH] core: REE FS: use a single file per object Prior to this commit each persistent object was represented by a directory with several files. With this commit each persistent object is represented by a single file instead to simplify the implementation. Reviewed-by: Jerome Forissier Acked-by: Etienne Carriere Tested-by: Jens Wiklander (QEMU) Signed-off-by: Jens Wiklander --- core/include/tee/tee_fs_key_manager.h | 18 + core/tee/tee_ree_fs.c | 599 +++++------------- .../secure_storage/tee_file_structure.odg | Bin 15680 -> 10810 bytes .../secure_storage/tee_file_structure.png | Bin 12995 -> 6570 bytes documentation/secure_storage.md | 75 +-- 5 files changed, 219 insertions(+), 473 deletions(-) diff --git a/core/include/tee/tee_fs_key_manager.h b/core/include/tee/tee_fs_key_manager.h index cbb5e11bec4..d874964a332 100644 --- a/core/include/tee/tee_fs_key_manager.h +++ b/core/include/tee/tee_fs_key_manager.h @@ -41,11 +41,29 @@ #define TEE_FS_KM_IV_LEN 12 /* bytes */ #define TEE_FS_KM_MAX_TAG_LEN 16 /* bytes */ + +#define BLOCK_FILE_SHIFT 12 + +#define BLOCK_FILE_SIZE (1 << BLOCK_FILE_SHIFT) + +#define NUM_BLOCKS_PER_FILE 1024 + enum tee_fs_file_type { META_FILE, BLOCK_FILE }; +struct tee_fs_file_info { + size_t length; + uint32_t backup_version_table[NUM_BLOCKS_PER_FILE / 32]; +}; + +struct tee_fs_file_meta { + struct tee_fs_file_info info; + uint8_t encrypted_fek[TEE_FS_KM_FEK_SIZE]; + uint32_t counter; +}; + struct common_header { uint8_t iv[TEE_FS_KM_IV_LEN]; uint8_t tag[TEE_FS_KM_MAX_TAG_LEN]; diff --git a/core/tee/tee_ree_fs.c b/core/tee/tee_ree_fs.c index 4ece81afaa9..cb918e342e6 100644 --- a/core/tee/tee_ree_fs.c +++ b/core/tee/tee_ree_fs.c @@ -45,34 +45,52 @@ #include #include -#define BLOCK_FILE_SHIFT 12 - -#define BLOCK_FILE_SIZE (1 << BLOCK_FILE_SHIFT) +/* + * This file implements the tee_file_operations structure for a secure + * filesystem based on single file in normal world. + * + * All fields in the REE file are duplicated with two versions 0 and 1. The + * active meta-data block is selected by the lowest bit in the + * meta-counter. The active file block is selected by corresponding bit + * number in struct tee_fs_file_info.backup_version_table. + * + * The atomicity of each operation is ensured by updating meta-counter when + * everything in the secondary blocks (both meta-data and file-data blocks) + * are successfully written. 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 REE filesystem, and + * has the following structure: + * + * [ 4 bytes meta-counter] + * [ meta-data version 0][ meta-data version 1 ] + * [ Block 0 version 0 ][ Block 0 version 1 ] + * [ Block 1 version 0 ][ Block 1 version 1 ] + * ... + * [ Block n version 0 ][ Block n version 1 ] + * + * One meta-data block is built up as: + * [ struct meta_header | struct tee_fs_get_header_size ] + * + * One data block is built up as: + * [ struct block_header | BLOCK_FILE_SIZE bytes ] + * + * struct meta_header and struct block_header are defined in + * tee_fs_key_manager.h. + * + */ #define MAX_NUM_CACHED_BLOCKS 1 -#define NUM_BLOCKS_PER_FILE 1024 #define MAX_FILE_SIZE (BLOCK_FILE_SIZE * NUM_BLOCKS_PER_FILE) -struct tee_fs_file_info { - size_t length; - uint32_t backup_version_table[NUM_BLOCKS_PER_FILE / 32]; -}; - -struct tee_fs_file_meta { - struct tee_fs_file_info info; - uint8_t encrypted_fek[TEE_FS_KM_FEK_SIZE]; - uint8_t backup_version; -}; - TAILQ_HEAD(block_head, block); struct block { TAILQ_ENTRY(block) list; int block_num; uint8_t *data; - size_t data_size; }; struct block_cache { @@ -81,11 +99,12 @@ struct block_cache { }; struct tee_fs_fd { + uint32_t meta_counter; struct tee_fs_file_meta meta; tee_fs_off_t pos; uint32_t flags; bool is_new_file; - char *filename; + int fd; struct block_cache block_cache; }; @@ -99,9 +118,8 @@ static inline int get_last_block_num(size_t size) return pos_to_block_num(size - 1); } -static inline uint8_t get_backup_version_of_block( - struct tee_fs_file_meta *meta, - size_t block_num) +static bool get_backup_version_of_block(struct tee_fs_file_meta *meta, + size_t block_num) { uint32_t index = (block_num / 32); uint32_t block_mask = 1 << (block_num % 32); @@ -136,36 +154,6 @@ struct block_operations { static struct mutex ree_fs_mutex = MUTEX_INITIALIZER; -/* - * We split a TEE file into multiple blocks and store them - * on REE filesystem. A TEE file is represented by a REE file - * called meta and a number of REE files called blocks. Meta - * file is used for storing file information, e.g. file size - * and backup version of each block. - * - * REE files naming rule is as follows: - * - * /meta. - * /block0. - * ... - * /block15. - * - * Backup_version is used to support atomic update operation. - * Original file will not be updated, instead we create a new - * version of the same file and update the new file instead. - * - * The backup_version of each block file is stored in meta - * file, the meta file itself also has backup_version, the update is - * successful after new version of meta has been written. - */ -#define REE_FS_NAME_MAX (TEE_FS_NAME_MAX + 20) - - -static int ree_fs_mkdir_rpc(const char *path, tee_fs_mode_t mode) -{ - return tee_fs_rpc_mkdir(OPTEE_MSG_RPC_CMD_FS, path, mode); -} - static TEE_Result ree_fs_opendir_rpc(const char *name, struct tee_fs_dir **d) { @@ -184,154 +172,129 @@ static TEE_Result ree_fs_readdir_rpc(struct tee_fs_dir *d, return tee_fs_rpc_new_readdir(OPTEE_MSG_RPC_CMD_FS, d, ent); } -static int ree_fs_access_rpc(const char *name, int mode) +static size_t meta_size(void) { - return tee_fs_rpc_access(OPTEE_MSG_RPC_CMD_FS, name, mode); + return tee_fs_get_header_size(META_FILE) + + sizeof(struct tee_fs_file_meta); } -static void get_meta_filepath(const char *file, int version, - char *meta_path) +static size_t meta_pos_raw(struct tee_fs_fd *fdp, bool active) { - snprintf(meta_path, REE_FS_NAME_MAX, "%s/meta.%d", - file, version); -} + size_t offs = sizeof(uint32_t); -static void get_block_filepath(const char *file, size_t block_num, - int version, char *meta_path) -{ - snprintf(meta_path, REE_FS_NAME_MAX, "%s/block%zu.%d", - file, block_num, version); + if ((fdp->meta_counter & 1) == active) + offs += meta_size(); + return offs; } -static int __remove_block_file(struct tee_fs_fd *fdp, size_t block_num, - bool toggle) +static size_t block_size_raw(void) { - TEE_Result res; - char block_path[REE_FS_NAME_MAX]; - uint8_t version = get_backup_version_of_block(&fdp->meta, block_num); - - if (toggle) - version = !version; - - get_block_filepath(fdp->filename, block_num, version, block_path); - DMSG("%s", block_path); - - res = tee_fs_rpc_new_remove(OPTEE_MSG_RPC_CMD_FS, block_path); - if (res == TEE_SUCCESS || res == TEE_ERROR_ITEM_NOT_FOUND) - return 0; /* ignore it if file not found */ - return -1; + return tee_fs_get_header_size(BLOCK_FILE) + BLOCK_FILE_SIZE; } -static int remove_block_file(struct tee_fs_fd *fdp, size_t block_num) +static size_t block_pos_raw(struct tee_fs_file_meta *meta, size_t block_num, + bool active) { - DMSG("remove block%zd", block_num); - return __remove_block_file(fdp, block_num, false); -} + size_t n = block_num * 2; -static int remove_outdated_block(struct tee_fs_fd *fdp, size_t block_num) -{ - DMSG("remove outdated block%zd", block_num); - return __remove_block_file(fdp, block_num, true); + if (active == get_backup_version_of_block(meta, block_num)) + n++; + + return sizeof(uint32_t) + meta_size() * 2 + n * block_size_raw(); } /* * encrypted_fek: as input for META_FILE and BLOCK_FILE */ -static TEE_Result encrypt_and_write_file(const char *file_name, - enum tee_fs_file_type file_type, +static TEE_Result encrypt_and_write_file(struct tee_fs_fd *fdp, + enum tee_fs_file_type file_type, size_t offs, void *data_in, size_t data_in_size, uint8_t *encrypted_fek) { TEE_Result res; - TEE_Result res2; struct tee_fs_rpc_operation op; void *ciphertext; size_t header_size = tee_fs_get_header_size(file_type); size_t ciphertext_size = header_size + data_in_size; - int fd; - res = tee_fs_rpc_new_open(OPTEE_MSG_RPC_CMD_FS, file_name, &fd); - if (res != TEE_SUCCESS) { - if (res != TEE_ERROR_ITEM_NOT_FOUND) - return res; - res = tee_fs_rpc_new_create(OPTEE_MSG_RPC_CMD_FS, file_name, - &fd); - if (res != TEE_SUCCESS) - return res; - } - res = tee_fs_rpc_new_write_init(&op, OPTEE_MSG_RPC_CMD_FS, fd, 0, - ciphertext_size, &ciphertext); + res = tee_fs_rpc_new_write_init(&op, OPTEE_MSG_RPC_CMD_FS, fdp->fd, + offs, ciphertext_size, &ciphertext); if (res != TEE_SUCCESS) - goto out; + return res; res = tee_fs_encrypt_file(file_type, data_in, data_in_size, ciphertext, &ciphertext_size, encrypted_fek); if (res != TEE_SUCCESS) - goto out; + return res; - res = tee_fs_rpc_new_write_final(&op); -out: - res2 = tee_fs_rpc_new_close(OPTEE_MSG_RPC_CMD_FS, fd); - if (res == TEE_SUCCESS) - return res2; - return res; + return tee_fs_rpc_new_write_final(&op); } /* * encrypted_fek: as output for META_FILE * as input for BLOCK_FILE */ -static TEE_Result read_and_decrypt_file(const char *file_name, - enum tee_fs_file_type file_type, +static TEE_Result read_and_decrypt_file(struct tee_fs_fd *fdp, + enum tee_fs_file_type file_type, size_t offs, void *data_out, size_t *data_out_size, uint8_t *encrypted_fek) { TEE_Result res; - TEE_Result res2; struct tee_fs_rpc_operation op; size_t bytes; void *ciphertext; - int fd; - - res = tee_fs_rpc_new_open(OPTEE_MSG_RPC_CMD_FS, file_name, &fd); - if (res != TEE_SUCCESS) - return res; bytes = *data_out_size + tee_fs_get_header_size(file_type); - res = tee_fs_rpc_new_read_init(&op, OPTEE_MSG_RPC_CMD_FS, fd, 0, + res = tee_fs_rpc_new_read_init(&op, OPTEE_MSG_RPC_CMD_FS, fdp->fd, offs, bytes, &ciphertext); if (res != TEE_SUCCESS) - goto out; + return res; res = tee_fs_rpc_new_read_final(&op, &bytes); if (res != TEE_SUCCESS) - goto out; + return res; + + if (!bytes) { + *data_out_size = 0; + return TEE_SUCCESS; + } res = tee_fs_decrypt_file(file_type, ciphertext, bytes, data_out, data_out_size, encrypted_fek); if (res != TEE_SUCCESS) - res = TEE_ERROR_CORRUPT_OBJECT; -out: - res2 = tee_fs_rpc_new_close(OPTEE_MSG_RPC_CMD_FS, fd); - if (res == TEE_SUCCESS) - return res2; - return res; + return TEE_ERROR_CORRUPT_OBJECT; + return TEE_SUCCESS; } -static TEE_Result write_meta_file(const char *filename, +static TEE_Result write_meta_file(struct tee_fs_fd *fdp, struct tee_fs_file_meta *meta) { - char meta_path[REE_FS_NAME_MAX]; + size_t offs = meta_pos_raw(fdp, false); - get_meta_filepath(filename, meta->backup_version, meta_path); - - return encrypt_and_write_file(meta_path, META_FILE, + return encrypt_and_write_file(fdp, META_FILE, offs, (void *)&meta->info, sizeof(meta->info), meta->encrypted_fek); } -static TEE_Result create_meta(struct tee_fs_fd *fdp) +static TEE_Result write_meta_counter(struct tee_fs_fd *fdp) +{ + TEE_Result res; + struct tee_fs_rpc_operation op; + size_t bytes = sizeof(uint32_t); + void *data; + + res = tee_fs_rpc_new_write_init(&op, OPTEE_MSG_RPC_CMD_FS, + fdp->fd, 0, bytes, &data); + if (res != TEE_SUCCESS) + return res; + + memcpy(data, &fdp->meta_counter, bytes); + + return tee_fs_rpc_new_write_final(&op); +} + +static TEE_Result create_meta(struct tee_fs_fd *fdp, const char *fname) { TEE_Result res; @@ -343,22 +306,26 @@ static TEE_Result create_meta(struct tee_fs_fd *fdp) if (res != TEE_SUCCESS) return res; - fdp->meta.backup_version = 0; + res = tee_fs_rpc_new_create(OPTEE_MSG_RPC_CMD_FS, fname, &fdp->fd); + if (res != TEE_SUCCESS) + return res; - return write_meta_file(fdp->filename, &fdp->meta); + fdp->meta.counter = fdp->meta_counter; + + res = write_meta_file(fdp, &fdp->meta); + if (res != TEE_SUCCESS) + return res; + return write_meta_counter(fdp); } static TEE_Result commit_meta_file(struct tee_fs_fd *fdp, struct tee_fs_file_meta *new_meta) { TEE_Result res; - uint8_t old_version; - char meta_path[REE_FS_NAME_MAX]; - old_version = new_meta->backup_version; - new_meta->backup_version = !new_meta->backup_version; + new_meta->counter = fdp->meta_counter + 1; - res = write_meta_file(fdp->filename, new_meta); + res = write_meta_file(fdp, new_meta); if (res != TEE_SUCCESS) return res; @@ -367,48 +334,59 @@ static TEE_Result commit_meta_file(struct tee_fs_fd *fdp, * change tee_fs_fd accordingly */ fdp->meta = *new_meta; + fdp->meta_counter = fdp->meta.counter; - /* - * Remove outdated meta file, there is nothing we can - * do if we fail here, but that is OK because both - * new & old version of block files are kept. The context - * of the file is still consistent. - */ - get_meta_filepath(fdp->filename, old_version, meta_path); - tee_fs_rpc_new_remove(OPTEE_MSG_RPC_CMD_FS, meta_path); - - return res; + return write_meta_counter(fdp); } -static TEE_Result read_meta_file(const char *meta_path, +static TEE_Result read_meta_file(struct tee_fs_fd *fdp, struct tee_fs_file_meta *meta) { size_t meta_info_size = sizeof(struct tee_fs_file_info); + size_t offs = meta_pos_raw(fdp, true); - return read_and_decrypt_file(meta_path, META_FILE, + return read_and_decrypt_file(fdp, META_FILE, offs, &meta->info, &meta_info_size, meta->encrypted_fek); } -static TEE_Result read_meta(struct tee_fs_fd *fdp) +static TEE_Result read_meta_counter(struct tee_fs_fd *fdp) { TEE_Result res; - char meta_path[REE_FS_NAME_MAX]; + struct tee_fs_rpc_operation op; + void *data; + size_t bytes = sizeof(uint32_t); - get_meta_filepath(fdp->filename, fdp->meta.backup_version, meta_path); - res = read_meta_file(meta_path, &fdp->meta); - if (res != TEE_SUCCESS) { - TEE_Result res2; - - fdp->meta.backup_version = !fdp->meta.backup_version; - get_meta_filepath(fdp->filename, fdp->meta.backup_version, - meta_path); - res2 = read_meta_file(meta_path, &fdp->meta); - if (res2 != TEE_ERROR_ITEM_NOT_FOUND) - return res2; - } + res = tee_fs_rpc_new_read_init(&op, OPTEE_MSG_RPC_CMD_FS, + fdp->fd, 0, bytes, &data); + if (res != TEE_SUCCESS) + return res; - return res; + res = tee_fs_rpc_new_read_final(&op, &bytes); + if (res != TEE_SUCCESS) + return res; + + if (bytes != sizeof(uint32_t)) + return TEE_ERROR_CORRUPT_OBJECT; + + memcpy(&fdp->meta_counter, data, bytes); + + return TEE_SUCCESS; +} + +static TEE_Result read_meta(struct tee_fs_fd *fdp, const char *fname) +{ + TEE_Result res; + + res = tee_fs_rpc_new_open(OPTEE_MSG_RPC_CMD_FS, fname, &fdp->fd); + if (res != TEE_SUCCESS) + return res; + + res = read_meta_counter(fdp); + if (res != TEE_SUCCESS) + return res; + + return read_meta_file(fdp, &fdp->meta); } static bool is_block_file_exist(struct tee_fs_file_meta *meta, @@ -427,26 +405,22 @@ static TEE_Result read_block_from_storage(struct tee_fs_fd *fdp, { TEE_Result res = TEE_SUCCESS; uint8_t *plaintext = b->data; - char block_path[REE_FS_NAME_MAX]; size_t block_file_size = BLOCK_FILE_SIZE; - uint8_t version = get_backup_version_of_block(&fdp->meta, b->block_num); + size_t offs = block_pos_raw(&fdp->meta, b->block_num, true); if (!is_block_file_exist(&fdp->meta, b->block_num)) goto exit; - get_block_filepath(fdp->filename, b->block_num, version, - block_path); - - res = read_and_decrypt_file(block_path, BLOCK_FILE, - plaintext, &block_file_size, - fdp->meta.encrypted_fek); + res = read_and_decrypt_file(fdp, BLOCK_FILE, offs, plaintext, + &block_file_size, fdp->meta.encrypted_fek); if (res != TEE_SUCCESS) { EMSG("Failed to read and decrypt file"); goto exit; } - b->data_size = block_file_size; - DMSG("Successfully read and decrypt block%d from storage, size=%zd", - b->block_num, b->data_size); + if (block_file_size != BLOCK_FILE_SIZE) + return TEE_ERROR_GENERIC; + DMSG("Successfully read and decrypt block%d from storage", + b->block_num); exit: return res; } @@ -456,21 +430,17 @@ static int flush_block_to_storage(struct tee_fs_fd *fdp, struct block *b, { TEE_Result res; size_t block_num = b->block_num; - char block_path[REE_FS_NAME_MAX]; - uint8_t new_version = - !get_backup_version_of_block(&fdp->meta, block_num); + size_t offs = block_pos_raw(&fdp->meta, b->block_num, false); - get_block_filepath(fdp->filename, block_num, new_version, block_path); - - res = encrypt_and_write_file(block_path, BLOCK_FILE, b->data, - b->data_size, new_meta->encrypted_fek); + res = encrypt_and_write_file(fdp, BLOCK_FILE, offs, b->data, + BLOCK_FILE_SIZE, new_meta->encrypted_fek); if (res != TEE_SUCCESS) { EMSG("Failed to encrypt and write block file"); goto fail; } - DMSG("Successfully encrypt and write block%d to storage, size=%zd", - b->block_num, b->data_size); + DMSG("Successfully encrypt and write block%d to storage", + b->block_num); toggle_backup_version_of_block(new_meta, block_num); return 0; @@ -493,7 +463,6 @@ static struct block *alloc_block(void) } c->block_num = -1; - c->data_size = 0; return c; @@ -597,19 +566,12 @@ static void write_data_to_block(struct block *b, int offset, { DMSG("Write %zd bytes to block%d", len, b->block_num); memcpy(b->data + offset, buf, len); - if (offset + len > b->data_size) { - b->data_size = offset + len; - DMSG("Extend block%d size to %zd bytes", - b->block_num, b->data_size); - } } static void read_data_from_block(struct block *b, int offset, void *buf, size_t len) { DMSG("Read %zd bytes from block%d", len, b->block_num); - if (offset + len > b->data_size) - panic("Exceeding block size"); memcpy(buf, b->data + offset, len); } @@ -704,61 +666,6 @@ static TEE_Result out_of_place_write(struct tee_fs_fd *fdp, const void *buf, return TEE_ERROR_GENERIC; } -static TEE_Result create_hard_link(const char *old_dir, const char *new_dir, - const char *filename) -{ - char old_path[REE_FS_NAME_MAX]; - char new_path[REE_FS_NAME_MAX]; - - snprintf(old_path, REE_FS_NAME_MAX, "%s/%s", - old_dir, filename); - snprintf(new_path, REE_FS_NAME_MAX, "%s/%s", - new_dir, filename); - - DMSG("%s -> %s", old_path, new_path); - if (tee_fs_rpc_link(OPTEE_MSG_RPC_CMD_FS, old_path, new_path)) - return TEE_ERROR_GENERIC; - - return TEE_SUCCESS; -} - -static TEE_Result unlink_tee_file(const char *file) -{ - TEE_Result res; - size_t len = strlen(file) + 1; - struct tee_fs_dirent *dirent; - struct tee_fs_dir *dir; - - DMSG("file=%s", file); - - if (len > TEE_FS_NAME_MAX) - return TEE_ERROR_GENERIC; - - res = ree_fs_opendir_rpc(file, &dir); - if (res != TEE_SUCCESS) - return res; - - res = ree_fs_readdir_rpc(dir, &dirent); - while (res == TEE_SUCCESS) { - char path[REE_FS_NAME_MAX]; - - snprintf(path, REE_FS_NAME_MAX, "%s/%s", - file, dirent->d_name); - - DMSG("unlink %s", path); - res = tee_fs_rpc_new_remove(OPTEE_MSG_RPC_CMD_FS, path); - if (res != TEE_SUCCESS) { - ree_fs_closedir_rpc(dir); - return res; - } - res = ree_fs_readdir_rpc(dir, &dirent); - } - - ree_fs_closedir_rpc(dir); - - return TEE_SUCCESS; -} - static TEE_Result open_internal(const char *file, bool create, bool overwrite, struct tee_file_handle **fh) { @@ -776,6 +683,7 @@ static TEE_Result open_internal(const char *file, bool create, bool overwrite, fdp = calloc(1, sizeof(struct tee_fs_fd)); if (!fdp) return TEE_ERROR_OUT_OF_MEMORY; + fdp->fd = -1; mutex_lock(&ree_fs_mutex); @@ -785,33 +693,30 @@ static TEE_Result open_internal(const char *file, bool create, bool overwrite, goto exit_free_fd; } - fdp->filename = strdup(file); - if (!fdp->filename) { - res = TEE_ERROR_OUT_OF_MEMORY; - goto exit_destroy_block_cache; - } - - res = read_meta(fdp); + res = read_meta(fdp, file); if (res == TEE_SUCCESS) { if (overwrite) { res = TEE_ERROR_ACCESS_CONFLICT; - goto exit_free_filename; + goto exit_close_file; } } else if (res == TEE_ERROR_ITEM_NOT_FOUND) { if (!create) - goto exit_free_filename; - res = create_meta(fdp); + goto exit_destroy_block_cache; + res = create_meta(fdp, file); if (res != TEE_SUCCESS) - goto exit_free_filename; + goto exit_close_file; } else { - goto exit_free_filename; + goto exit_destroy_block_cache; } *fh = (struct tee_file_handle *)fdp; goto exit; -exit_free_filename: - free(fdp->filename); +exit_close_file: + if (fdp->fd != -1) + tee_fs_rpc_new_close(OPTEE_MSG_RPC_CMD_FS, fdp->fd); + if (create) + tee_fs_rpc_new_remove(OPTEE_MSG_RPC_CMD_FS, file); exit_destroy_block_cache: destroy_block_cache(&fdp->block_cache); exit_free_fd: @@ -838,7 +743,7 @@ static void ree_fs_close(struct tee_file_handle **fh) if (fdp) { destroy_block_cache(&fdp->block_cache); - free(fdp->filename); + tee_fs_rpc_new_close(OPTEE_MSG_RPC_CMD_FS, fdp->fd); free(fdp); *fh = NULL; } @@ -899,7 +804,6 @@ static TEE_Result ree_fs_seek(struct tee_file_handle *fh, int32_t offset, * * - update file length to new length * - commit new meta - * - free unused blocks * * To ensure atomic extend operation, we can: * @@ -931,25 +835,11 @@ static TEE_Result ree_fs_ftruncate_internal(struct tee_fs_fd *fdp, new_meta.info.length = new_file_len; if ((size_t)new_file_len < old_file_len) { - int old_block_num = get_last_block_num(old_file_len); - int new_block_num = get_last_block_num(new_file_len); - DMSG("Truncate file length to %zu", (size_t)new_file_len); res = commit_meta_file(fdp, &new_meta); if (res != TEE_SUCCESS) return res; - - /* now we are safe to free unused blocks */ - while (old_block_num > new_block_num) { - if (remove_block_file(fdp, old_block_num)) { - IMSG("Warning: Failed to free block: %d", - old_block_num); - } - - old_block_num--; - } - } else { size_t ext_len = new_file_len - old_file_len; int orig_pos = fdp->pos; @@ -1020,12 +910,8 @@ static TEE_Result ree_fs_read(struct tee_file_handle *fh, void *buf, goto exit; } - DMSG("%s, data len=%zu", fdp->filename, remain_bytes); - start_block_num = pos_to_block_num(fdp->pos); end_block_num = pos_to_block_num(fdp->pos + remain_bytes - 1); - DMSG("start_block_num:%d, end_block_num:%d", - start_block_num, end_block_num); while (start_block_num <= end_block_num) { struct block *b; @@ -1036,9 +922,6 @@ static TEE_Result ree_fs_read(struct tee_file_handle *fh, void *buf, if (size_to_read + offset > BLOCK_FILE_SIZE) size_to_read = BLOCK_FILE_SIZE - offset; - DMSG("block_num:%d, offset:%d, size_to_read: %zd", - start_block_num, offset, size_to_read); - b = block_ops.read(fdp, start_block_num); if (!b) { res = TEE_ERROR_CORRUPT_OBJECT; @@ -1073,15 +956,6 @@ static TEE_Result ree_fs_read(struct tee_file_handle *fh, void *buf, * * (Any failure in above steps is considered as update failed, * and the file content will not be updated) - * - * After previous step the update is considered complete, but - * we should do the following clean-up step(s): - * - * - Delete old meta file. - * - Remove old block files. - * - * (Any failure in above steps is considered as a successfully - * update) */ static TEE_Result ree_fs_write(struct tee_file_handle *fh, const void *buf, size_t len) @@ -1090,9 +964,6 @@ static TEE_Result ree_fs_write(struct tee_file_handle *fh, const void *buf, struct tee_fs_file_meta new_meta; struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; size_t file_size; - tee_fs_off_t orig_pos; - int start_block_num; - int end_block_num; if (!len) @@ -1101,17 +972,13 @@ static TEE_Result ree_fs_write(struct tee_file_handle *fh, const void *buf, mutex_lock(&ree_fs_mutex); file_size = fdp->meta.info.length; - orig_pos = fdp->pos; if ((fdp->pos + len) > MAX_FILE_SIZE || (fdp->pos + len) < len) { - EMSG("Over maximum file size(%d)", MAX_FILE_SIZE); res = TEE_ERROR_BAD_PARAMETERS; goto exit; } - DMSG("%s, data len=%zu", fdp->filename, len); if (file_size < (size_t)fdp->pos) { - DMSG("File hole detected, try to extend file size"); res = ree_fs_ftruncate_internal(fdp, fdp->pos); if (res != TEE_SUCCESS) goto exit; @@ -1123,56 +990,16 @@ static TEE_Result ree_fs_write(struct tee_file_handle *fh, const void *buf, goto exit; res = commit_meta_file(fdp, &new_meta); - if (res != TEE_SUCCESS) - goto exit; - - /* we are safe to free old blocks */ - start_block_num = pos_to_block_num(orig_pos); - end_block_num = pos_to_block_num(fdp->pos - 1); - while (start_block_num <= end_block_num) { - if (remove_outdated_block(fdp, start_block_num)) - IMSG("Warning: Failed to free old block: %d", - start_block_num); - - start_block_num++; - } exit: mutex_unlock(&ree_fs_mutex); return res; } -/* - * To ensure atomicity of rename operation, we need to - * do the following steps: - * - * - Create a new folder that represents the renamed TEE file - * - For each REE block files, create a hard link under the just - * created folder (new TEE file) - * - Now we are ready to commit meta, create hard link for the - * meta file - * - * (Any failure in above steps is considered as update failed, - * and the file content will not be updated) - * - * After previous step the update is considered complete, but - * we should do the following clean-up step(s): - * - * - Unlink all REE files represents the old TEE file (including - * meta and block files) - * - * (Any failure in above steps is considered as a successfully - * update) - */ static TEE_Result ree_fs_rename_internal(const char *old, const char *new, bool overwrite) { - TEE_Result res; size_t old_len; size_t new_len; - size_t meta_count = 0; - struct tee_fs_dir *old_dir; - struct tee_fs_dirent *dirent; - char *meta_filename = NULL; DMSG("old=%s, new=%s", old, new); @@ -1182,66 +1009,7 @@ static TEE_Result ree_fs_rename_internal(const char *old, const char *new, if (old_len > TEE_FS_NAME_MAX || new_len > TEE_FS_NAME_MAX) return TEE_ERROR_BAD_PARAMETERS; - if (!ree_fs_access_rpc(new, TEE_FS_F_OK)) { - if (!overwrite) - return TEE_ERROR_ACCESS_CONFLICT; - unlink_tee_file(new); - } - - if (ree_fs_mkdir_rpc(new, TEE_FS_S_IRUSR | TEE_FS_S_IWUSR)) - return TEE_ERROR_GENERIC; - - res = ree_fs_opendir_rpc(old, &old_dir); - if (res != TEE_SUCCESS) - return res; - - res = ree_fs_readdir_rpc(old_dir, &dirent); - while (res == TEE_SUCCESS) { - if (!strncmp(dirent->d_name, "meta.", 5)) { - meta_filename = strdup(dirent->d_name); - meta_count++; - } else { - res = create_hard_link(old, new, dirent->d_name); - if (res != TEE_SUCCESS) - goto exit_close_old_dir; - } - - res = ree_fs_readdir_rpc(old_dir, &dirent); - } - - /* finally, link the meta file, rename operation completed */ - if (!meta_filename) - panic("no meta file"); - - /* - * TODO: This will cause memory leakage at previous strdup() - * if we accidently have two meta files in a TEE file. - * - * It's not easy to handle the case above (e.g. Which meta file - * should be linked first? What to do if a power cut happened - * during creating links for the two meta files?) - * - * We will solve this issue using another approach: merging - * both meta and block files into a single REE file. This approach - * can completely remove ree_fs_rename(). We can simply - * rename TEE file using REE rename() system call, which is also - * atomic. - */ - if (meta_count > 1) - EMSG("Warning: more than one meta file in your TEE file\n" - "This will cause memory leakage."); - - res = create_hard_link(old, new, meta_filename); - if (res != TEE_SUCCESS) - goto exit_close_old_dir; - - /* we are safe now, remove old TEE file */ - unlink_tee_file(old); - -exit_close_old_dir: - ree_fs_closedir_rpc(old_dir); - free(meta_filename); - return res; + return tee_fs_rpc_new_rename(OPTEE_MSG_RPC_CMD_FS, old, new, overwrite); } static TEE_Result ree_fs_rename(const char *old, const char *new, @@ -1256,37 +1024,14 @@ static TEE_Result ree_fs_rename(const char *old, const char *new, return res; } -/* - * To ensure atomic unlink operation, we can simply - * split the unlink operation into: - * - * - rename("file", "file.trash"); - * - * (Any failure in above steps is considered as update failed, - * and the file content will not be updated) - * - * After previous step the update is considered complete, but - * we should do the following clean-up step(s): - * - * - unlink("file.trash"); - * - * (Any failure in above steps is considered as a successfully - * update) - */ static TEE_Result ree_fs_remove(const char *file) { TEE_Result res; - char trash_file[TEE_FS_NAME_MAX + 6]; - - snprintf(trash_file, TEE_FS_NAME_MAX + 6, "%s.trash", file); mutex_lock(&ree_fs_mutex); - - res = ree_fs_rename_internal(file, trash_file, true); - if (res == TEE_SUCCESS) - unlink_tee_file(trash_file); - + res = tee_fs_rpc_new_remove(OPTEE_MSG_RPC_CMD_FS, file); mutex_unlock(&ree_fs_mutex); + return res; } diff --git a/documentation/images/secure_storage/tee_file_structure.odg b/documentation/images/secure_storage/tee_file_structure.odg index d7c980c583dc2c9689b6119d38bc3cd55e4173b0..1742f27577e636af833778fc2ad9333c09bab2ef 100644 GIT binary patch delta 8539 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

R^EGe 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 diff --git a/documentation/secure_storage.md b/documentation/secure_storage.md index d286bee8d0b..2ad89a9c9ab 100644 --- a/documentation/secure_storage.md +++ b/documentation/secure_storage.md @@ -85,19 +85,37 @@ Below is an excerpt from the specification listing the most vital requirements: OP-TEE by default use "/data/tee/" as the secure storage space in Linux file system. For each TA, OP-TEE use the TA's UUID to create a standalone folder for it under the secure storage space folder. For a persistent object belonging -to a specific TA, OP-TEE creates a TEE file folder which's name is object-id -under the TA folder. +to a specific TA, OP-TEE creates a TEE file is object-id under the TA folder. + +All fields in the REE file are duplicated with two versions 0 and 1. The +active meta-data block is selected by the lowest bit in the +meta-counter. The active file block is selected by corresponding bit +number instruct tee_fs_file_info.backup_version_table. + +The atomicity of each operation is ensured by updating meta-counter when +everything in the secondary blocks (both meta-data and file-data blocks) +are successfully written. The main purpose of the code 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 REE filesystem, and has the following structure: +``` +[ 4 bytes meta-counter] +[ meta-data version 0][ meta-data version 1 ] +[ Block 0 version 0 ][ Block 0 version 1 ] +[ Block 1 version 0 ][ Block 1 version 1 ] +... +[ Block n version 0 ][ Block n version 1 ] +``` -In a TEE file folder, there is a meta file and several block files. Meta file is -for storing the information of the TEE file which is used by TEE file system to -manage the TEE file; block file is for storing the data of the persistent -object. +One meta-data block is built up as: +``` +[ struct meta_header | struct tee_fs_get_header_size ] +``` -If the compile time flag CFG_ENC_FS is set to 'y', the data stored in block -files will be encrypted, otherwise, the data will not be encrypted. The -information stored in meta file are always encrypted. By default, CFG_ENC_FS is -set to 'y' to keep the confidentiality of TEE files; It is recommended to -change CFG_ENC_FS to 'n' only for TA debugging. +One data block is built up as: +``` +[ struct block_header | BLOCK_FILE_SIZE bytes ] +``` The reason why we store the TEE file content in many small blocks is to accelerate the file update speed when handling a large file. The block size @@ -185,41 +203,6 @@ following operations should support atomic update: The strategy used in OP-TEE secure storage to guarantee the atomicity is out-of-place update. -### Out-Of-Place Update - -When modifying a meta file or a block file, TEE file system should create a -backup version of the file and then modify it. For the block file, the backup -version is kept in its filename and in meta data, and for the meta file, the -backup version is only kept in filename. - -Naming rule as follows: -``` -meta. -block. -``` - -### 3-Stage Update - -An atomic operation can be split into three stages as described below: - -- **Out-Of-Place update stage** - -In this stage, TEE file system will do out-of-place update on the meta file -or the block files to be modified. Any failure occurring at this stage will -cause the operation to fail and no changes will be made. - -- **Commit stage** - -In this stage, TEE file system will commit the new meta file into Linux file -system and delete the old meta file. Any failure occurring at this stage will -cause the operation to fail and no changes will be made. - -- **Clean up stage** - -In this stage, TEE file system will clean up unnecessary or old block files. -If an error occurs, the operation still be treated as a success but some -garbage files may be left over in Linux file system. - ## Future Work - **TA storage space isolation**