/* * Copyright (c) 2014, STMicroelectronics International N.V. * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __aligned #define __aligned(x) __attribute__((__aligned__(x))) #endif #include #ifndef PATH_MAX #define PATH_MAX 255 #endif /* Path to all secure storage files. */ static char tee_fs_root[PATH_MAX]; #define TEE_FS_FILENAME_MAX_LENGTH 150 static pthread_mutex_t dir_handle_db_mutex = PTHREAD_MUTEX_INITIALIZER; static struct handle_db dir_handle_db = HANDLE_DB_INITIALIZER_WITH_MUTEX(&dir_handle_db_mutex); static TEEC_Result errno_to_teec(int err) { switch (err) { case ENOSPC: return TEEC_ERROR_STORAGE_NO_SPACE; case ENOENT: return TEEC_ERROR_ITEM_NOT_FOUND; default: break; } return TEEC_ERROR_GENERIC; } static size_t tee_fs_get_absolute_filename(char *file, char *out, size_t out_size) { int s = 0; if (!file || !out || (out_size <= strlen(tee_fs_root) + 1)) return 0; s = snprintf(out, out_size, "%s%s", tee_fs_root, file); if (s < 0 || (size_t)s >= out_size) return 0; /* Safe to cast since we have checked that sizes are OK */ return (size_t)s; } static void fs_fsync(void) { int fd = 0; fd = open(tee_fs_root, O_RDONLY | O_DIRECTORY); if (fd > 0) { fsync(fd); close(fd); } } static int do_mkdir(const char *path, mode_t mode) { struct stat st; memset(&st, 0, sizeof(st)); if (mkdir(path, mode) != 0 && errno != EEXIST) return -1; if (stat(path, &st) != 0 && !S_ISDIR(st.st_mode)) return -1; fs_fsync(); return 0; } static int mkpath(const char *path, mode_t mode) { int status = 0; char *subpath = strdup(path); char *prev = subpath; char *curr = NULL; while (status == 0 && (curr = strchr(prev, '/')) != 0) { /* * Check for root or double slash */ if (curr != prev) { *curr = '\0'; status = do_mkdir(subpath, mode); *curr = '/'; } prev = curr + 1; } if (status == 0) status = do_mkdir(path, mode); free(subpath); return status; } static int tee_supp_fs_init(void) { size_t n = 0; mode_t mode = 0700; n = snprintf(tee_fs_root, sizeof(tee_fs_root), "%s/", supplicant_params.fs_parent_path); if (n >= sizeof(tee_fs_root)) return -1; if (mkpath(tee_fs_root, mode) != 0) return -1; return 0; } static int open_wrapper(const char *fname, int flags) { int fd = 0; while (true) { fd = open(fname, flags | O_SYNC, 0600); if (fd >= 0 || errno != EINTR) return fd; } } static TEEC_Result ree_fs_new_open(size_t num_params, struct tee_ioctl_param *params) { char abs_filename[PATH_MAX] = { 0 }; char *fname = NULL; int fd = 0; if (num_params != 3 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT || (params[2].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT) return TEEC_ERROR_BAD_PARAMETERS; fname = tee_supp_param_to_va(params + 1); if (!fname) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(fname, abs_filename, sizeof(abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; fd = open_wrapper(abs_filename, O_RDWR); if (fd < 0) { /* * In case the problem is the filesystem is RO, retry with the * open flags restricted to RO. */ fd = open_wrapper(abs_filename, O_RDONLY); if (fd < 0) return TEEC_ERROR_ITEM_NOT_FOUND; } params[2].a = fd; return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_create(size_t num_params, struct tee_ioctl_param *params) { char abs_filename[PATH_MAX] = { 0 }; char abs_dir[PATH_MAX] = { 0 }; char *fname = NULL; char *d = NULL; int fd = 0; const int flags = O_RDWR | O_CREAT | O_TRUNC; if (num_params != 3 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT || (params[2].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT) return TEEC_ERROR_BAD_PARAMETERS; fname = tee_supp_param_to_va(params + 1); if (!fname) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(fname, abs_filename, sizeof(abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; fd = open_wrapper(abs_filename, flags); if (fd >= 0) goto out; if (errno != ENOENT) return errno_to_teec(errno); /* Directory for file missing, try make to it */ strncpy(abs_dir, abs_filename, sizeof(abs_dir)); abs_dir[sizeof(abs_dir) - 1] = '\0'; d = dirname(abs_dir); if (!mkdir(d, 0700)) { int err = 0; fd = open_wrapper(abs_filename, flags); if (fd >= 0) goto out; /* * The directory was made but the file could still not be * created. */ err = errno; rmdir(d); return errno_to_teec(err); } if (errno != ENOENT) return errno_to_teec(errno); /* Parent directory for file missing, try to make it */ d = dirname(d); if (mkdir(d, 0700)) return errno_to_teec(errno); /* Try to make directory for file again */ strncpy(abs_dir, abs_filename, sizeof(abs_dir)); abs_dir[sizeof(abs_dir) - 1] = '\0'; d = dirname(abs_dir); if (mkdir(d, 0700)) { int err = errno; d = dirname(d); rmdir(d); return errno_to_teec(err); } fd = open_wrapper(abs_filename, flags); if (fd < 0) { int err = errno; rmdir(d); d = dirname(d); rmdir(d); return errno_to_teec(err); } out: fs_fsync(); params[2].a = fd; return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_close(size_t num_params, struct tee_ioctl_param *params) { int fd = 0; if (num_params != 1 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) return TEEC_ERROR_BAD_PARAMETERS; fd = params[0].b; while (close(fd)) { if (errno != EINTR) return errno_to_teec(errno); } return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_read(size_t num_params, struct tee_ioctl_param *params) { uint8_t *buf = NULL; size_t len = 0; off_t offs = 0; int fd = 0; ssize_t r = 0; size_t s = 0; if (num_params != 2 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) return TEEC_ERROR_BAD_PARAMETERS; fd = params[0].b; offs = params[0].c; buf = tee_supp_param_to_va(params + 1); if (!buf) return TEEC_ERROR_BAD_PARAMETERS; len = MEMREF_SIZE(params + 1); s = 0; r = -1; while (r && len) { r = pread(fd, buf, len, offs); if (r < 0) { if (errno == EINTR) continue; return errno_to_teec(errno); } assert((size_t)r <= len); buf += r; len -= r; offs += r; s += r; } MEMREF_SIZE(params + 1) = s; return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_write(size_t num_params, struct tee_ioctl_param *params) { uint8_t *buf = NULL; size_t len = 0; off_t offs = 0; int fd = 0; ssize_t r = 0; if (num_params != 2 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) return TEEC_ERROR_BAD_PARAMETERS; fd = params[0].b; offs = params[0].c; buf = tee_supp_param_to_va(params + 1); if (!buf) return TEEC_ERROR_BAD_PARAMETERS; len = MEMREF_SIZE(params + 1); while (len) { r = pwrite(fd, buf, len, offs); if (r < 0) { if (errno == EINTR) continue; return errno_to_teec(errno); } assert((size_t)r <= len); buf += r; len -= r; offs += r; } return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_truncate(size_t num_params, struct tee_ioctl_param *params) { size_t len = 0; int fd = 0; if (num_params != 1 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) return TEEC_ERROR_BAD_PARAMETERS; fd = params[0].b; len = params[0].c; while (ftruncate(fd, len)) { if (errno != EINTR) return errno_to_teec(errno); } return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_remove(size_t num_params, struct tee_ioctl_param *params) { char abs_filename[PATH_MAX] = { 0 }; char *fname = NULL; char *d = NULL; if (num_params != 2 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) return TEEC_ERROR_BAD_PARAMETERS; fname = tee_supp_param_to_va(params + 1); if (!fname) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(fname, abs_filename, sizeof(abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; if (unlink(abs_filename)) return errno_to_teec(errno); /* If a file is removed, maybe the directory can be removed to? */ d = dirname(abs_filename); if (!rmdir(d)) { /* * If the directory was removed, maybe the parent directory * can be removed too? */ d = dirname(d); rmdir(d); } return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_rename(size_t num_params, struct tee_ioctl_param *params) { char old_abs_filename[PATH_MAX] = { 0 }; char new_abs_filename[PATH_MAX] = { 0 }; char *old_fname = NULL; char *new_fname = NULL; bool overwrite = false; if (num_params != 3 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT || (params[2].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) return TEEC_ERROR_BAD_PARAMETERS; overwrite = !!params[0].b; old_fname = tee_supp_param_to_va(params + 1); if (!old_fname) return TEEC_ERROR_BAD_PARAMETERS; new_fname = tee_supp_param_to_va(params + 2); if (!new_fname) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(old_fname, old_abs_filename, sizeof(old_abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(new_fname, new_abs_filename, sizeof(new_abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; if (!overwrite) { struct stat st; if (!stat(new_abs_filename, &st)) return TEEC_ERROR_ACCESS_CONFLICT; } if (rename(old_abs_filename, new_abs_filename)) { if (errno == ENOENT) return TEEC_ERROR_ITEM_NOT_FOUND; } fs_fsync(); return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_opendir(size_t num_params, struct tee_ioctl_param *params) { char abs_filename[PATH_MAX] = { 0 }; char *fname = NULL; DIR *dir = NULL; int handle = 0; struct dirent *dent = NULL; bool empty = true; if (num_params != 3 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT || (params[2].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT) return TEEC_ERROR_BAD_PARAMETERS; fname = tee_supp_param_to_va(params + 1); if (!fname) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_get_absolute_filename(fname, abs_filename, sizeof(abs_filename))) return TEEC_ERROR_BAD_PARAMETERS; dir = opendir(abs_filename); if (!dir) return TEEC_ERROR_ITEM_NOT_FOUND; /* * Ignore empty directories. Works around an issue when the * data path is mounted over NFS. Due to the way OP-TEE implements * TEE_CloseAndDeletePersistentObject1() currently, tee-supplicant * still has a file descriptor open to the file when it's removed in * ree_fs_new_remove(). In this case the NFS server may create a * temporary reference called .nfs????, and the rmdir() call fails * so that the TA directory is left over. Checking this special case * here avoids that TEE_StartPersistentObjectEnumerator() returns * TEE_SUCCESS when it should return TEEC_ERROR_ITEM_NOT_FOUND. * Test case: "xtest 6009 6010". */ while ((dent = readdir(dir))) { if (dent->d_name[0] == '.') continue; empty = false; break; } if (empty) { closedir(dir); return TEEC_ERROR_ITEM_NOT_FOUND; } rewinddir(dir); handle = handle_get(&dir_handle_db, dir); if (handle < 0) { closedir(dir); return TEEC_ERROR_OUT_OF_MEMORY; } params[2].a = handle; return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_closedir(size_t num_params, struct tee_ioctl_param *params) { DIR *dir = NULL; if (num_params != 1 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) return TEEC_ERROR_BAD_PARAMETERS; dir = handle_put(&dir_handle_db, params[0].b); if (!dir) return TEEC_ERROR_BAD_PARAMETERS; closedir(dir); return TEEC_SUCCESS; } static TEEC_Result ree_fs_new_readdir(size_t num_params, struct tee_ioctl_param *params) { DIR *dir = NULL; struct dirent *dirent = NULL; char *buf = NULL; size_t len = 0; size_t fname_len = 0; if (num_params != 2 || (params[0].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT || (params[1].attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) != TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) return TEEC_ERROR_BAD_PARAMETERS; buf = tee_supp_param_to_va(params + 1); if (!buf) return TEEC_ERROR_BAD_PARAMETERS; len = MEMREF_SIZE(params + 1); dir = handle_lookup(&dir_handle_db, params[0].b); if (!dir) return TEEC_ERROR_BAD_PARAMETERS; while (true) { dirent = readdir(dir); if (!dirent) return TEEC_ERROR_ITEM_NOT_FOUND; if (dirent->d_name[0] != '.') break; } fname_len = strlen(dirent->d_name) + 1; MEMREF_SIZE(params + 1) = fname_len; if (fname_len > len) return TEEC_ERROR_SHORT_BUFFER; memcpy(buf, dirent->d_name, fname_len); return TEEC_SUCCESS; } TEEC_Result tee_supp_fs_process(size_t num_params, struct tee_ioctl_param *params) { if (!num_params || !tee_supp_param_is_value(params)) return TEEC_ERROR_BAD_PARAMETERS; if (!tee_fs_root[0]) { if (tee_supp_fs_init() != 0) { EMSG("error tee_supp_fs_init: failed to create %s/", tee_fs_root); memset(tee_fs_root, 0, sizeof(tee_fs_root)); return TEEC_ERROR_STORAGE_NOT_AVAILABLE; } } switch (params->a) { case OPTEE_MRF_OPEN: return ree_fs_new_open(num_params, params); case OPTEE_MRF_CREATE: return ree_fs_new_create(num_params, params); case OPTEE_MRF_CLOSE: return ree_fs_new_close(num_params, params); case OPTEE_MRF_READ: return ree_fs_new_read(num_params, params); case OPTEE_MRF_WRITE: return ree_fs_new_write(num_params, params); case OPTEE_MRF_TRUNCATE: return ree_fs_new_truncate(num_params, params); case OPTEE_MRF_REMOVE: return ree_fs_new_remove(num_params, params); case OPTEE_MRF_RENAME: return ree_fs_new_rename(num_params, params); case OPTEE_MRF_OPENDIR: return ree_fs_new_opendir(num_params, params); case OPTEE_MRF_CLOSEDIR: return ree_fs_new_closedir(num_params, params); case OPTEE_MRF_READDIR: return ree_fs_new_readdir(num_params, params); default: return TEEC_ERROR_BAD_PARAMETERS; } }