blob: 187a1dbbfabbdf5f560f7d293b0f1183ac268853 [file] [log] [blame]
/*
* 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.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <handle.h>
#include <libgen.h>
#include <optee_msg_supplicant.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <teec_trace.h>
#include <tee_supp_fs.h>
#include <tee_supplicant.h>
#include <unistd.h>
#ifndef __aligned
#define __aligned(x) __attribute__((__aligned__(x)))
#endif
#include <linux/tee.h>
#ifndef PATH_MAX
#define PATH_MAX 255
#endif
/* Path to all secure storage files. */
static int tee_fs_fd = -1;
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_relative_filename(char *file, char *out,
size_t out_size)
{
int s = 0;
if (!file || !out || (out_size <= strlen(file) + 3))
return 0;
s = snprintf(out, out_size, "./%s", 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)
{
if (tee_fs_fd > 0)
fsync(tee_fs_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;
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)
{
mode_t mode = 0700;
if (mkpath(supplicant_params.fs_parent_path, mode) != 0)
return -1;
tee_fs_fd = open(supplicant_params.fs_parent_path, O_RDONLY);
if (tee_fs_fd < 0)
return -1;
fs_fsync();
return 0;
}
static int openat_wrapper(const char *fname, int flags)
{
int fd = 0;
while (true) {
fd = openat(tee_fs_fd, 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 rel_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_relative_filename(fname, rel_filename,
sizeof(rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
fd = openat_wrapper(rel_filename, O_RDWR);
if (fd < 0) {
/*
* In case the problem is the filesystem is RO, retry with the
* open flags restricted to RO.
*/
fd = openat_wrapper(rel_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 rel_filename[PATH_MAX] = { 0 };
char rel_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_relative_filename(fname, rel_filename,
sizeof(rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
fd = openat_wrapper(rel_filename, flags);
if (fd >= 0)
goto out;
if (errno != ENOENT)
return errno_to_teec(errno);
/* Directory for file missing, try to make it */
strncpy(rel_dir, rel_filename, sizeof(rel_dir));
rel_dir[sizeof(rel_dir) - 1] = '\0';
d = dirname(rel_dir);
if (!mkdirat(tee_fs_fd, d, 0700)) {
int err = 0;
fd = openat_wrapper(rel_filename, flags);
if (fd >= 0)
goto out;
/*
* The directory was made but the file could still not be
* created.
*/
err = errno;
unlinkat(tee_fs_fd, d, AT_REMOVEDIR);
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 (mkdirat(tee_fs_fd, d, 0700))
return errno_to_teec(errno);
/* Try to make directory for file again */
d = dirname(rel_dir);
if (mkdirat(tee_fs_fd, d, 0700)) {
int err = errno;
d = dirname(d);
unlinkat(tee_fs_fd, d, AT_REMOVEDIR);
return errno_to_teec(err);
}
fd = openat_wrapper(rel_filename, flags);
if (fd < 0) {
int err = errno;
unlinkat(tee_fs_fd, d, AT_REMOVEDIR);
d = dirname(d);
unlinkat(tee_fs_fd, d, AT_REMOVEDIR);
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 rel_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_relative_filename(fname, rel_filename,
sizeof(rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
if (unlinkat(tee_fs_fd, rel_filename, 0))
return errno_to_teec(errno);
/* If a file is removed, maybe the directory can be removed to? */
d = dirname(rel_filename);
if (!unlinkat(tee_fs_fd, d, AT_REMOVEDIR)) {
/*
* If the directory was removed, maybe the parent directory
* can be removed too?
*/
d = dirname(d);
unlinkat(tee_fs_fd, d, AT_REMOVEDIR);
}
return TEEC_SUCCESS;
}
static TEEC_Result ree_fs_new_rename(size_t num_params,
struct tee_ioctl_param *params)
{
char old_rel_filename[PATH_MAX] = { 0 };
char new_rel_filename[PATH_MAX] = { 0 };
char *old_fname = NULL;
char *new_fname = NULL;
bool overwrite = false;
int flags = 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_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_relative_filename(old_fname, old_rel_filename,
sizeof(old_rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
if (!tee_fs_get_relative_filename(new_fname, new_rel_filename,
sizeof(new_rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
if (!overwrite)
flags = RENAME_NOREPLACE;
if (renameat2(tee_fs_fd, old_rel_filename, tee_fs_fd, new_rel_filename, flags)) {
if (errno == EEXIST)
return TEEC_ERROR_ACCESS_CONFLICT;
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 rel_filename[PATH_MAX] = { 0 };
char *fname = NULL;
int dir_fd = -1;
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_relative_filename(fname, rel_filename,
sizeof(rel_filename)))
return TEEC_ERROR_BAD_PARAMETERS;
dir_fd = openat_wrapper(rel_filename, O_DIRECTORY);
if (dir_fd < 0)
return TEEC_ERROR_ITEM_NOT_FOUND;
dir = fdopendir(dir_fd);
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_fd == -1) {
if (tee_supp_fs_init() != 0) {
EMSG("error tee_supp_fs_init: failed to create %s/",
supplicant_params.fs_parent_path);
tee_fs_fd = -1;
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;
}
}