* session_manager.c
*
* function for session management
*
* Copyright (c) 2012-2022 Huawei Technologies Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "session_manager.h"
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/file.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/spinlock_types.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <asm/cacheflush.h>
#include <linux/kthread.h>
#include <linux/atomic.h>
#include <linux/vmalloc.h>
#include <linux/pid.h>
#include <linux/cred.h>
#include <linux/thread_info.h>
#include <linux/highmem.h>
#include <linux/mm.h>
#if (KERNEL_VERSION(4, 14, 0) <= LINUX_VERSION_CODE)
#include <linux/sched/mm.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#endif
#include <linux/completion.h>
#ifndef CONFIG_CONFIDENTIAL_CONTAINER
#include <linux/proc_ns.h>
#endif
#include <securec.h>
#include "smc_smp.h"
#include "mem.h"
#include "gp_ops.h"
#include "tc_ns_log.h"
#include "teek_client_constants.h"
#include "client_hash_auth.h"
#include "mailbox_mempool.h"
#include "tc_client_driver.h"
#include "internal_functions.h"
#include "ko_adapt.h"
#include "shared_mem.h"
#include "tee_compat_check.h"
static DEFINE_MUTEX(g_load_app_lock);
#define MAX_REF_COUNT (255)
struct list_head g_service_list;
DEFINE_MUTEX(g_service_list_lock);
static int lock_interruptible(struct mutex *m)
{
int ret;
do {
ret = mutex_lock_interruptible(m);
if (ret != 0) {
if (sigkill_pending(current))
return ret;
tloge("signal try relock ret %d", ret);
continue;
}
} while (0);
return 0;
}
void init_srvc_list(void)
{
INIT_LIST_HEAD(&g_service_list);
}
#ifdef CONFIG_REGISTER_SHAREDMEM
static void release_session_register_sharedmem(struct tc_ns_session *session)
{
if (session->register_sharedmem.buf_size != 0) {
release_shared_mem_page(session->register_sharedmem.buf, session->register_sharedmem.buf_size);
mailbox_free(session->register_sharedmem.buf);
session->register_sharedmem.buf = 0;
session->register_sharedmem.buf_size = 0;
}
}
#endif
void get_session_struct(struct tc_ns_session *session)
{
if (!session)
return;
atomic_inc(&session->usage);
}
void put_session_struct(struct tc_ns_session *session)
{
if (!session || !atomic_dec_and_test(&session->usage))
return;
#ifdef CONFIG_REGISTER_SHAREDMEM
release_session_register_sharedmem(session);
#endif
if (memset_s(session, sizeof(*session), 0, sizeof(*session)) != 0)
tloge("Caution, memset failed!\n");
kfree(session);
}
void get_service_struct(struct tc_ns_service *service)
{
if (!service)
return;
atomic_inc(&service->usage);
tlogd("service->usage = %d\n", atomic_read(&service->usage));
}
void put_service_struct(struct tc_ns_service *service)
{
if (!service)
return;
tlogd("service->usage = %d\n", atomic_read(&service->usage));
mutex_lock(&g_service_list_lock);
if (atomic_dec_and_test(&service->usage)) {
tlogd("del service [0x%x] from service list\n",
*(uint32_t *)service->uuid);
list_del(&service->head);
kfree(service);
}
mutex_unlock(&g_service_list_lock);
}
static int add_service_to_dev(struct tc_ns_dev_file *dev,
struct tc_ns_service *service)
{
uint32_t i;
if (!dev || !service)
return -EINVAL;
for (i = 0; i < SERVICES_MAX_COUNT; i++) {
if (!dev->services[i]) {
tlogd("add service %u to %u\n", i, dev->dev_file_id);
dev->services[i] = service;
dev->service_ref[i] = 1;
return 0;
}
}
return -EFAULT;
}
static void tz_srv_sess_dump(const char *param)
{
struct tc_ns_smc_cmd smc_cmd = { {0}, 0 };
(void)param;
smc_cmd.cmd_id = GLOBAL_CMD_ID_DUMP_SRV_SESS;
smc_cmd.cmd_type = CMD_TYPE_GLOBAL;
livepatch_down_read_sem();
if (tc_ns_smc(&smc_cmd))
tloge("send dump service session failed\n");
livepatch_up_read_sem();
}
void dump_services_status(const char *param)
{
struct tc_ns_service *service = NULL;
(void)param;
mutex_lock(&g_service_list_lock);
tlogi("show service list:\n");
list_for_each_entry(service, &g_service_list, head) {
tlogi("uuid-%x, usage=%d\n", *(uint32_t *)service->uuid,
atomic_read(&service->usage));
}
mutex_unlock(&g_service_list_lock);
tz_srv_sess_dump(param);
}
static void del_service_from_dev(struct tc_ns_dev_file *dev,
struct tc_ns_service *service)
{
uint32_t i;
for (i = 0; i < SERVICES_MAX_COUNT; i++) {
if (dev->services[i] == service) {
tlogd("dev service ref-%u = %u\n", i,
dev->service_ref[i]);
if (dev->service_ref[i] == 0) {
tloge("Caution! No service to be deleted!\n");
break;
}
dev->service_ref[i]--;
if (dev->service_ref[i] == 0) {
tlogd("del service %u from %u\n",
i, dev->dev_file_id);
dev->services[i] = NULL;
put_service_struct(service);
}
break;
}
}
}
struct tc_ns_session *tc_find_session_withowner(
const struct list_head *session_list,
unsigned int session_id, const struct tc_ns_dev_file *dev_file)
{
struct tc_ns_session *session = NULL;
if (!session_list || !dev_file) {
tloge("session list or dev is null\n");
return NULL;
}
list_for_each_entry(session, session_list, head) {
if (session->session_id == session_id &&
session->owner == dev_file)
return session;
}
return NULL;
}
struct tc_ns_service *tc_find_service_in_dev(const struct tc_ns_dev_file *dev,
const unsigned char *uuid, int uuid_size)
{
uint32_t i;
if (!dev || !uuid || uuid_size != UUID_LEN)
return NULL;
for (i = 0; i < SERVICES_MAX_COUNT; i++) {
if (dev->services[i] != NULL &&
memcmp(dev->services[i]->uuid, uuid, UUID_LEN) == 0)
return dev->services[i];
}
return NULL;
}
struct tc_ns_session *tc_find_session_by_uuid(unsigned int dev_file_id,
const struct tc_ns_smc_cmd *cmd)
{
struct tc_ns_dev_file *dev_file = NULL;
struct tc_ns_service *service = NULL;
struct tc_ns_session *session = NULL;
if (!cmd) {
tloge("parameter is null pointer!\n");
return NULL;
}
dev_file = tc_find_dev_file(dev_file_id);
if (!dev_file) {
tloge("can't find dev file!\n");
return NULL;
}
mutex_lock(&dev_file->service_lock);
service = tc_find_service_in_dev(dev_file, cmd->uuid, UUID_LEN);
get_service_struct(service);
mutex_unlock(&dev_file->service_lock);
if (!service) {
tloge("can't find service!\n");
return NULL;
}
mutex_lock(&service->session_lock);
session = tc_find_session_withowner(&service->session_list,
cmd->context_id, dev_file);
get_session_struct(session);
mutex_unlock(&service->session_lock);
put_service_struct(service);
if (!session) {
tloge("can't find session-0x%x!\n", cmd->context_id);
return NULL;
}
return session;
}
static int tc_ns_need_load_image(const struct tc_ns_dev_file *dev_file,
const unsigned char *uuid, unsigned int uuid_len, struct tc_ns_client_return *tee_ret)
{
int ret;
int smc_ret;
struct tc_ns_smc_cmd smc_cmd = { {0}, 0 };
struct mb_cmd_pack *mb_pack = NULL;
char *mb_param = NULL;
mb_pack = mailbox_alloc_cmd_pack();
if (!mb_pack) {
tloge("alloc mb pack failed\n");
return -ENOMEM;
}
mb_param = mailbox_copy_alloc(uuid, uuid_len);
if (!mb_param) {
tloge("alloc mb param failed\n");
ret = -ENOMEM;
goto clean;
}
mb_pack->operation.paramtypes = TEEC_MEMREF_TEMP_INOUT;
mb_pack->operation.params[0].memref.buffer =
mailbox_virt_to_phys((uintptr_t)mb_param);
mb_pack->operation.buffer_h_addr[0] =
(uint64_t)mailbox_virt_to_phys((uintptr_t)mb_param) >> ADDR_TRANS_NUM;
mb_pack->operation.params[0].memref.size = SZ_4K;
smc_cmd.cmd_id = GLOBAL_CMD_ID_NEED_LOAD_APP;
smc_cmd.cmd_type = CMD_TYPE_GLOBAL;
smc_cmd.dev_file_id = dev_file->dev_file_id;
#ifdef CONFIG_CONFIDENTIAL_CONTAINER
smc_cmd.nsid = dev_file->nsid;
smc_cmd.vmid = dev_file->vmid;
#endif
smc_cmd.context_id = 0;
smc_cmd.operation_phys = mailbox_virt_to_phys((uintptr_t)&mb_pack->operation);
smc_cmd.operation_h_phys =
(uint64_t)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation) >> ADDR_TRANS_NUM;
smc_ret = tc_ns_smc_skip_kill(&smc_cmd);
if (smc_ret != 0) {
tloge("smc call returns error ret 0x%x\n", smc_ret);
if (smc_cmd.err_origin != TEEC_ORIGIN_COMMS && tee_ret != NULL) {
tee_ret->origin = smc_cmd.err_origin;
tee_ret->code = smc_ret;
}
ret = -EFAULT;
goto clean;
} else {
ret = *(int *)mb_param;
}
clean:
if (mb_param)
mailbox_free(mb_param);
mailbox_free(mb_pack);
return ret;
}
static int init_ioctl_arg(struct tc_ns_dev_file *dev_file, const void __user *argp,
const struct load_secfile_ioctl_struct *k_argp, struct load_secfile_ioctl_struct *ioctl_arg)
{
if (!dev_file) {
tloge("dev file is null\n");
return -EINVAL;
}
if (dev_file->kernel_api != TEE_REQ_FROM_KERNEL_MODE) {
if (!argp) {
tloge("argp is null\n");
return -EINVAL;
}
if (copy_from_user(ioctl_arg, argp, sizeof(*ioctl_arg))) {
tloge("copy from user failed\n");
return -ENOMEM;
}
} else {
if (!k_argp) {
tloge("k_argp is null\n");
return -EINVAL;
}
if (memcpy_s(ioctl_arg, sizeof(*ioctl_arg), k_argp, sizeof(*ioctl_arg)) != EOK) {
tloge("memcpy arg err\n");
return -ENOMEM;
}
}
return 0;
}
int tc_ns_load_secfile(struct tc_ns_dev_file *dev_file,
void __user *argp, const struct load_secfile_ioctl_struct *k_argp, bool is_from_client_node)
{
int ret;
struct load_secfile_ioctl_struct ioctl_arg = { {0}, {0}, {NULL} };
bool load = true;
void *file_addr = NULL;
ret = init_ioctl_arg(dev_file, argp, k_argp, &ioctl_arg);
if (ret != 0) {
tloge("init ioctl args failed, ret %d\n", ret);
return ret;
}
if (ioctl_arg.sec_file_info.secfile_type >= LOAD_TYPE_MAX ||
ioctl_arg.sec_file_info.secfile_type == LOAD_PATCH) {
tloge("invalid secfile type: %d!", ioctl_arg.sec_file_info.secfile_type);
return -EINVAL;
}
mutex_lock(&g_load_app_lock);
if (is_from_client_node) {
if (ioctl_arg.sec_file_info.secfile_type != LOAD_TA &&
ioctl_arg.sec_file_info.secfile_type != LOAD_LIB) {
tloge("this node does not allow this type of file to be loaded\n");
mutex_unlock(&g_load_app_lock);
return -EINVAL;
}
}
if (ioctl_arg.sec_file_info.secfile_type == LOAD_TA) {
ret = tc_ns_need_load_image(dev_file, ioctl_arg.uuid, (unsigned int)UUID_LEN, NULL);
if (ret != 1)
load = false;
}
if (load) {
file_addr = (void *)(uintptr_t)(ioctl_arg.memref.file_addr |
(((uint64_t)ioctl_arg.memref.file_h_addr) << ADDR_TRANS_NUM));
ret = tc_ns_load_image(dev_file, file_addr, &ioctl_arg.sec_file_info, NULL);
if (ret != 0)
tloge("load TA secfile: %d failed, ret = 0x%x\n",
ioctl_arg.sec_file_info.secfile_type, ret);
}
mutex_unlock(&g_load_app_lock);
if (dev_file->kernel_api != TEE_REQ_FROM_KERNEL_MODE) {
if (copy_to_user(argp, &ioctl_arg, sizeof(ioctl_arg)) != 0)
tloge("copy to user failed\n");
}
return ret;
}
static uint32_t tc_ns_get_uid(void)
{
struct task_struct *task = NULL;
const struct cred *cred = NULL;
uint32_t uid;
rcu_read_lock();
task = get_current();
get_task_struct(task);
rcu_read_unlock();
cred = koadpt_get_task_cred(task);
if (!cred) {
tloge("failed to get uid of the task\n");
put_task_struct(task);
return (uint32_t)(-1);
}
uid = cred->uid.val;
put_cred(cred);
put_task_struct(task);
tlogd("current uid is %u\n", uid);
return uid;
}
#ifdef CONFIG_AUTH_SUPPORT_UNAME
static int set_login_information_uname(struct tc_ns_dev_file *dev_file, uint32_t uid)
{
char uname[MAX_NAME_LENGTH] = { 0 };
uint32_t username_len = 0;
int ret = tc_ns_get_uname(uid, uname, sizeof(uname), &username_len);
if (ret < 0 || username_len >= MAX_NAME_LENGTH) {
tloge("get user name filed\n");
return -EFAULT;
}
if (memcpy_s(dev_file->pub_key, MAX_PUBKEY_LEN, uname, username_len)) {
tloge("failed to copy username, pub key len=%u\n", dev_file->pub_key_len);
return -EFAULT;
}
dev_file->pub_key_len = username_len;
return 0;
}
#else
static int set_login_information_uid(struct tc_ns_dev_file *dev_file, uint32_t ca_uid)
{
if (memcpy_s(dev_file->pub_key, MAX_PUBKEY_LEN, &ca_uid, sizeof(ca_uid)) != 0) {
tloge("failed to copy pubkey, pub key len=%u\n",
dev_file->pub_key_len);
return -EFAULT;
}
dev_file->pub_key_len = sizeof(ca_uid);
return 0;
}
#endif
* Modify the client context so params id 2 and 3 contain temp pointers to the
* public key and package name for the open session. This is used for the
* TEEC_LOGIN_IDENTIFY open session method
*/
static int set_login_information(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context)
{
uint64_t size_addr, buffer_addr;
if (dev_file->pkg_name_len == 0)
return -EINVAL;
* The 3rd parameter buffer points to the pkg name buffer in the
* device file pointer
* get package name len and package name
*/
size_addr = (__u64)(uintptr_t)&dev_file->pkg_name_len;
buffer_addr = (__u64)(uintptr_t)dev_file->pkg_name;
context->params[3].memref.size_addr = (__u32)size_addr;
context->params[3].memref.size_h_addr = (__u32)(size_addr >> ADDR_TRANS_NUM);
context->params[3].memref.buffer = (__u32)buffer_addr;
context->params[3].memref.buffer_h_addr = (__u32)(buffer_addr >> ADDR_TRANS_NUM);
if (dev_file->pub_key_len == 0) {
uint32_t ca_uid = tc_ns_get_uid();
if (ca_uid == (uint32_t)(-1)) {
tloge("failed to get uid of the task\n");
goto error;
}
#ifdef CONFIG_AUTH_SUPPORT_UNAME
if (set_login_information_uname(dev_file, ca_uid) != 0)
goto error;
#else
if (set_login_information_uid(dev_file, ca_uid) != 0)
goto error;
#endif
#ifdef CONFIG_AUTH_HASH
dev_file->pkg_name_len = strlen((unsigned char *)dev_file->pkg_name);
#endif
}
size_addr = (__u64)(uintptr_t)&dev_file->pub_key_len;
buffer_addr = (__u64)(uintptr_t)dev_file->pub_key;
context->params[2].memref.size_addr = (__u32)size_addr;
context->params[2].memref.size_h_addr = (__u32)(size_addr >> ADDR_TRANS_NUM);
context->params[2].memref.buffer = (__u32)buffer_addr;
context->params[2].memref.buffer_h_addr = (__u32)(buffer_addr >> ADDR_TRANS_NUM);
context->param_types = teec_param_types(
teec_param_type_get(context->param_types, 0),
teec_param_type_get(context->param_types, 1),
TEEC_MEMREF_TEMP_INPUT, TEEC_MEMREF_TEMP_INPUT);
#ifdef CONFIG_AUTH_HASH
if(set_login_information_hash(dev_file) != 0) {
tloge("set login information hash failed\n");
goto error;
}
#endif
return 0;
error:
return -EFAULT;
}
static int check_login_method(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context, uint8_t *flags)
{
int ret;
if (!dev_file || !context || !flags)
return -EFAULT;
if (is_tee_rebooting()) {
context->returns.code = TEE_ERROR_IS_DEAD;
return EFAULT;
}
if (context->login.method != TEEC_LOGIN_IDENTIFY) {
tloge("login method is not supported\n");
return -EINVAL;
}
tlogd("login method is IDENTIFY\n");
ret = set_login_information(dev_file, context);
if (ret != 0) {
tloge("set login information failed ret =%d\n", ret);
return ret;
}
*flags |= TC_CALL_LOGIN;
return 0;
}
static struct tc_ns_service *tc_ref_service_in_dev(struct tc_ns_dev_file *dev,
const unsigned char *uuid, int uuid_size, bool *is_full)
{
uint32_t i;
if (uuid_size != UUID_LEN)
return NULL;
for (i = 0; i < SERVICES_MAX_COUNT; i++) {
if (dev->services[i] != NULL &&
is_same_group(dev->nsid, dev->vmid, dev->services[i]->nsid, dev->services[i]->vmid) &&
memcmp(dev->services[i]->uuid, uuid, UUID_LEN) == 0) {
if (dev->service_ref[i] == MAX_REF_COUNT) {
*is_full = true;
return NULL;
}
dev->service_ref[i]++;
return dev->services[i];
}
}
return NULL;
}
static int tc_ns_service_init(const unsigned char *uuid, uint32_t uuid_len, uint32_t nsid, uint32_t vmid,
struct tc_ns_service **new_service)
{
int ret = 0;
struct tc_ns_service *service = NULL;
if (!uuid || !new_service || uuid_len != UUID_LEN)
return -EINVAL;
service = kzalloc(sizeof(*service), GFP_KERNEL);
if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)service)) {
tloge("kzalloc failed\n");
ret = -ENOMEM;
return ret;
}
if (memcpy_s(service->uuid, sizeof(service->uuid), uuid, uuid_len) != 0) {
kfree(service);
return -EFAULT;
}
service->nsid = nsid;
service->vmid = vmid;
INIT_LIST_HEAD(&service->session_list);
mutex_init(&service->session_lock);
list_add_tail(&service->head, &g_service_list);
tlogd("add service: 0x%x to service list\n", *(const uint32_t *)uuid);
atomic_set(&service->usage, 1);
mutex_init(&service->operation_lock);
*new_service = service;
return ret;
}
static struct tc_ns_service *tc_find_service_from_all(
const unsigned char *uuid, uint32_t uuid_len, uint32_t nsid, uint32_t vmid)
{
struct tc_ns_service *service = NULL;
if (!uuid || uuid_len != UUID_LEN)
return NULL;
list_for_each_entry(service, &g_service_list, head) {
if (memcmp(service->uuid, uuid, sizeof(service->uuid)) == 0 &&
is_same_group(nsid, vmid, service->nsid, service->vmid))
return service;
}
return NULL;
}
static struct tc_ns_service *find_service(struct tc_ns_dev_file *dev_file,
const struct tc_ns_client_context *context)
{
int ret;
struct tc_ns_service *service = NULL;
bool is_full = false;
unsigned int nsid = dev_file->nsid;
unsigned int vmid = dev_file->vmid;
mutex_lock(&dev_file->service_lock);
service = tc_ref_service_in_dev(dev_file, context->uuid,
UUID_LEN, &is_full);
if (service || is_full) {
* If service has been reference by this dev, find service in dev
* will incre ref count to declaim there's how many callers to
* this service from the dev, instead of incre service->usage.
* While close session, dev->service_ref[i] will decre and till
* it get to 0, put service struct will be called.
*/
mutex_unlock(&dev_file->service_lock);
return service;
}
mutex_lock(&g_service_list_lock);
service = tc_find_service_from_all(context->uuid, UUID_LEN, nsid, vmid);
if (service) {
get_service_struct(service);
mutex_unlock(&g_service_list_lock);
goto add_service;
}
ret = tc_ns_service_init(context->uuid, UUID_LEN, nsid, vmid, &service);
mutex_unlock(&g_service_list_lock);
if (ret != 0) {
tloge("service init failed");
mutex_unlock(&dev_file->service_lock);
return NULL;
}
add_service:
ret = add_service_to_dev(dev_file, service);
mutex_unlock(&dev_file->service_lock);
if (ret != 0) {
* for new srvc, match init usage to 1;
* for srvc already exist, match get;
*/
put_service_struct(service);
service = NULL;
tloge("fail to add service to dev\n");
return NULL;
}
return service;
}
static bool is_valid_ta_size(const char *file_buffer, unsigned int file_size)
{
if (!file_buffer || file_size == 0) {
tloge("invalid load ta size\n");
return false;
}
if (file_size > SZ_8M) {
tloge("not support TA larger than 8M, size=%u\n", file_size);
return false;
}
return true;
}
static int alloc_for_load_image(struct load_img_params *params)
{
for (; params->mb_load_size > 0; params->mb_load_size >>= 1) {
params->mb_load_mem = mailbox_alloc(params->mb_load_size, 0);
if (params->mb_load_mem)
break;
tlogw("alloc mem size=%u for TA load mem fail\n",
params->mb_load_size);
}
if (!params->mb_load_mem) {
tloge("alloc TA load mem failed\n");
return -ENOMEM;
}
params->mb_pack = mailbox_alloc_cmd_pack();
if (!params->mb_pack) {
mailbox_free(params->mb_load_mem);
params->mb_load_mem = NULL;
tloge("alloc mb pack failed\n");
return -ENOMEM;
}
params->uuid_return = mailbox_alloc(sizeof(*(params->uuid_return)), 0);
if (!params->uuid_return) {
mailbox_free(params->mb_load_mem);
params->mb_load_mem = NULL;
mailbox_free(params->mb_pack);
params->mb_pack = NULL;
tloge("alloc uuid failed\n");
return -ENOMEM;
}
return 0;
}
static void pack_load_frame_cmd(uint32_t load_size,
const struct load_img_params *params, struct tc_ns_smc_cmd *smc_cmd)
{
struct mb_cmd_pack *mb_pack = params->mb_pack;
char *mb_load_mem = params->mb_load_mem;
struct tc_uuid *uuid_return = params->uuid_return;
mb_pack->operation.params[0].memref.buffer =
mailbox_virt_to_phys((uintptr_t)mb_load_mem);
mb_pack->operation.buffer_h_addr[0] =
(uint64_t)mailbox_virt_to_phys((uintptr_t)mb_load_mem) >> ADDR_TRANS_NUM;
mb_pack->operation.params[0].memref.size = load_size + sizeof(int);
mb_pack->operation.params[2].memref.buffer =
mailbox_virt_to_phys((uintptr_t)uuid_return);
mb_pack->operation.buffer_h_addr[2] =
(uint64_t)mailbox_virt_to_phys((uintptr_t)uuid_return) >> ADDR_TRANS_NUM;
mb_pack->operation.params[2].memref.size = sizeof(*uuid_return);
mb_pack->operation.paramtypes = teec_param_types(TEEC_MEMREF_TEMP_INPUT,
TEEC_VALUE_INOUT, TEEC_MEMREF_TEMP_OUTPUT, TEEC_VALUE_INOUT);
smc_cmd->cmd_type = CMD_TYPE_GLOBAL;
smc_cmd->cmd_id = GLOBAL_CMD_ID_LOAD_SECURE_APP;
smc_cmd->context_id = 0;
smc_cmd->operation_phys = mailbox_virt_to_phys((uintptr_t)&mb_pack->operation);
smc_cmd->operation_h_phys =
(uint64_t)mailbox_virt_to_phys((uintptr_t)&mb_pack->operation) >> ADDR_TRANS_NUM;
}
static int32_t load_image_copy_file(struct load_img_params *params, uint32_t load_size,
int32_t load_flag, uint32_t loaded_size)
{
if (params->dev_file->kernel_api == TEE_REQ_FROM_KERNEL_MODE) {
if (memcpy_s(params->mb_load_mem + sizeof(load_flag),
params->mb_load_size - sizeof(load_flag),
params->file_buffer + loaded_size, load_size) != 0) {
tloge("memcpy file buf get fail\n");
return -EFAULT;
}
return 0;
}
if (copy_from_user(params->mb_load_mem + sizeof(load_flag),
(const void __user *)params->file_buffer + loaded_size, load_size)) {
tloge("file buf get fail\n");
return -EFAULT;
}
return 0;
}
static int load_image_by_frame(struct load_img_params *params, unsigned int load_times,
struct tc_ns_client_return *tee_ret, struct sec_file_info *sec_file_info)
{
char *p = params->mb_load_mem;
uint32_t load_size;
int load_flag = 1;
uint32_t loaded_size = 0;
unsigned int index;
struct tc_ns_smc_cmd smc_cmd = { {0}, 0 };
int smc_ret;
for (index = 0; index < load_times; index++) {
smc_cmd.err_origin = TEEC_ORIGIN_COMMS;
if (index == (load_times - 1)) {
load_flag = 0;
load_size = params->file_size - loaded_size;
} else {
load_size = params->mb_load_size - sizeof(load_flag);
}
*(int *)p = load_flag;
if (load_size > params->mb_load_size - sizeof(load_flag)) {
tloge("invalid load size %u/%u\n", load_size,
params->mb_load_size);
return -EINVAL;
}
if (load_image_copy_file(params, load_size, load_flag, loaded_size) != 0)
return -EFAULT;
pack_load_frame_cmd(load_size, params, &smc_cmd);
params->mb_pack->operation.params[3].value.a = index;
params->mb_pack->operation.params[1].value.a = sec_file_info->secfile_type;
smc_cmd.dev_file_id = params->dev_file->dev_file_id;
#ifdef CONFIG_CONFIDENTIAL_CONTAINER
smc_cmd.nsid = params->dev_file->nsid;
smc_cmd.vmid = params->dev_file->vmid;
#endif
smc_ret = tc_ns_smc_skip_kill(&smc_cmd);
tlogd("configid=%u, ret=%d, load_flag=%d, index=%u\n",
params->mb_pack->operation.params[1].value.a, smc_ret,
load_flag, index);
if (smc_ret != 0) {
if (tee_ret != NULL) {
tee_ret->code = smc_ret;
tee_ret->origin = smc_cmd.err_origin;
}
sec_file_info->sec_load_err = (int32_t)params->mb_pack->operation.params[3].value.b;
return -EFAULT;
}
if (!smc_ret && !load_flag && load_image_for_ion(params, tee_ret ? &tee_ret->origin : NULL))
return -EPERM;
loaded_size += load_size;
}
return 0;
}
int tc_ns_load_image_with_lock(struct tc_ns_dev_file *dev, const char *file_buffer,
unsigned int file_size, enum secfile_type_t type)
{
int ret;
struct sec_file_info sec_file = {0, 0, 0};
if (!dev || !file_buffer) {
tloge("dev or file buffer is NULL!\n");
return -EINVAL;
}
sec_file.secfile_type = type;
sec_file.file_size = file_size;
mutex_lock(&g_load_app_lock);
ret = tc_ns_load_image(dev, file_buffer, &sec_file, NULL);
mutex_unlock(&g_load_app_lock);
return ret;
}
static void free_load_image_buffer(struct load_img_params *params)
{
mailbox_free(params->mb_load_mem);
mailbox_free(params->mb_pack);
mailbox_free(params->uuid_return);
}
int load_image(struct load_img_params *params,
struct sec_file_info *sec_file_info, struct tc_ns_client_return *tee_ret)
{
int ret;
unsigned int load_times;
unsigned int file_size;
if (params == NULL || sec_file_info == NULL)
return -1;
file_size = params->file_size;
params->mb_load_size = (file_size > (SZ_1M - sizeof(int))) ?
SZ_1M : ALIGN(file_size, SZ_4K);
ret = alloc_for_load_image(params);
if (ret != 0) {
tloge("Alloc load image buf fail!\n");
return ret;
}
if (params->mb_load_size <= sizeof(int)) {
tloge("mb load size is too small!\n");
free_load_image_buffer(params);
return -ENOMEM;
}
load_times = file_size / (params->mb_load_size - sizeof(int));
if ((file_size % (params->mb_load_size - sizeof(int))) != 0)
load_times += 1;
ret = load_image_by_frame(params, load_times, tee_ret, sec_file_info);
if (ret != 0) {
tloge("load image by frame fail!\n");
free_load_image_buffer(params);
return ret;
}
free_load_image_buffer(params);
return 0;
}
int tc_ns_load_image(struct tc_ns_dev_file *dev, const char *file_buffer,
struct sec_file_info *sec_file_info, struct tc_ns_client_return *tee_ret)
{
unsigned int file_size;
struct load_img_params params = { dev, file_buffer, 0, NULL, NULL, NULL, 0 };
if (!dev || !file_buffer || !sec_file_info) {
tloge("dev or file buffer or sec_file_info is NULL!\n");
return -EINVAL;
}
file_size = sec_file_info->file_size;
params.file_size = file_size;
if (!is_valid_ta_size(file_buffer, file_size))
return -EINVAL;
return load_image(¶ms, sec_file_info, tee_ret);
}
static int load_ta_image(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context)
{
int ret;
struct sec_file_info sec_file = {0, 0, 0};
struct tc_ns_client_return tee_ret = {0};
void *file_addr = NULL;
tee_ret.origin = TEEC_ORIGIN_COMMS;
mutex_lock(&g_load_app_lock);
ret = tc_ns_need_load_image(dev_file, context->uuid, (unsigned int)UUID_LEN, &tee_ret);
if (ret == 1) {
if (!context->file_buffer) {
tloge("context's file_buffer is NULL");
mutex_unlock(&g_load_app_lock);
return -1;
}
file_addr = (void *)(uintptr_t)(context->memref.file_addr |
(((uint64_t)context->memref.file_h_addr) << ADDR_TRANS_NUM));
sec_file.secfile_type = LOAD_TA;
sec_file.file_size = context->file_size;
ret = tc_ns_load_image(dev_file, file_addr, &sec_file, &tee_ret);
if (ret != 0) {
tloge("load image failed, ret=%x", ret);
context->returns.code = tee_ret.code;
if (tee_ret.origin != TEEC_ORIGIN_COMMS) {
context->returns.origin = tee_ret.origin;
ret = EFAULT;
}
mutex_unlock(&g_load_app_lock);
return ret;
}
}
if (ret != 0 && tee_ret.origin != TEEC_ORIGIN_COMMS) {
context->returns.code = tee_ret.code;
context->returns.origin = tee_ret.origin;
ret = EFAULT;
}
mutex_unlock(&g_load_app_lock);
return ret;
}
static void init_new_sess_node(struct tc_ns_dev_file *dev_file,
const struct tc_ns_client_context *context,
struct tc_ns_service *service,
struct tc_ns_session *session)
{
session->session_id = context->session_id;
atomic_set(&session->usage, 1);
session->owner = dev_file;
session->wait_data.send_wait_flag = 0;
init_waitqueue_head(&session->wait_data.send_cmd_wq);
mutex_lock(&service->session_lock);
list_add_tail(&session->head, &service->session_list);
mutex_unlock(&service->session_lock);
}
static int proc_open_session(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context, struct tc_ns_service *service,
struct tc_ns_session *session, uint8_t flags)
{
int ret;
struct tc_call_params params = {
dev_file, context, session, flags
};
if (lock_interruptible(&service->operation_lock) != 0)
return -EINTR;
ret = load_ta_image(dev_file, context);
if (ret != 0) {
tloge("load ta image failed\n");
mutex_unlock(&service->operation_lock);
return ret;
}
ret = tc_client_call(¶ms);
if (ret != 0) {
kill_ion_by_uuid((struct tc_uuid *)context->uuid);
mutex_unlock(&service->operation_lock);
tloge("smc call returns error, ret=0x%x\n", ret);
return ret;
}
init_new_sess_node(dev_file, context, service, session);
* session_id in tee is unique, but in concurrency scene
* same session_id may appear in tzdriver, put session_list
* add/del in service->operation_lock can avoid it.
*/
mutex_unlock(&service->operation_lock);
return ret;
}
static void clear_context_param(struct tc_ns_client_context *context)
{
context->params[2].memref.size_addr = 0;
context->params[2].memref.size_h_addr = 0;
context->params[2].memref.buffer = 0;
context->params[2].memref.buffer_h_addr = 0;
context->params[3].memref.size_addr = 0;
context->params[3].memref.size_h_addr = 0;
context->params[3].memref.buffer = 0;
context->params[3].memref.buffer_h_addr = 0;
}
int tc_ns_open_session(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context)
{
int ret;
struct tc_ns_service *service = NULL;
struct tc_ns_session *session = NULL;
uint8_t flags = TC_CALL_GLOBAL;
if (!dev_file || !context) {
tloge("invalid dev_file or context\n");
return -EINVAL;
}
ret = check_login_method(dev_file, context, &flags);
if (ret != 0)
goto err_clear_param;
context->cmd_id = GLOBAL_CMD_ID_OPEN_SESSION;
service = find_service(dev_file, context);
if (!service) {
tloge("find service failed\n");
ret = -ENOMEM;
goto err_clear_param;
}
session = kzalloc(sizeof(*session), GFP_KERNEL);
if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)session)) {
tloge("kzalloc failed\n");
mutex_lock(&dev_file->service_lock);
del_service_from_dev(dev_file, service);
mutex_unlock(&dev_file->service_lock);
ret = -ENOMEM;
goto err_clear_param;
}
mutex_init(&session->ta_session_lock);
ret = calc_client_auth_hash(dev_file, context, session);
if (ret != 0) {
tloge("calc client auth hash failed\n");
goto err_free_rsrc;
}
ret = proc_open_session(dev_file, context, service, session, flags);
if (ret == 0)
goto err_clear_param;
err_free_rsrc:
mutex_lock(&dev_file->service_lock);
del_service_from_dev(dev_file, service);
mutex_unlock(&dev_file->service_lock);
#ifdef CONFIG_REGISTER_SHAREDMEM
release_session_register_sharedmem(session);
#endif
kfree(session);
err_clear_param:
clear_context_param(context);
return ret;
}
static struct tc_ns_session *get_session(struct tc_ns_service *service,
const struct tc_ns_dev_file *dev_file,
const struct tc_ns_client_context *context)
{
struct tc_ns_session *session = NULL;
mutex_lock(&service->session_lock);
session = tc_find_session_withowner(&service->session_list,
context->session_id, dev_file);
get_session_struct(session);
mutex_unlock(&service->session_lock);
return session;
}
static struct tc_ns_service *get_service(struct tc_ns_dev_file *dev_file,
const struct tc_ns_client_context *context)
{
struct tc_ns_service *service = NULL;
mutex_lock(&dev_file->service_lock);
service = tc_find_service_in_dev(dev_file, context->uuid, UUID_LEN);
get_service_struct(service);
mutex_unlock(&dev_file->service_lock);
return service;
}
static int close_session(struct tc_ns_dev_file *dev,
struct tc_ns_session *session, const unsigned char *uuid,
unsigned int uuid_len, unsigned int session_id)
{
struct tc_ns_client_context context;
int ret;
struct tc_call_params params = {
dev, &context, session, 0
};
if (uuid_len != UUID_LEN)
return -EINVAL;
if (memset_s(&context, sizeof(context), 0, sizeof(context)) != 0)
return -EFAULT;
if (memcpy_s(context.uuid, sizeof(context.uuid), uuid, uuid_len) != 0)
return -EFAULT;
context.session_id = session_id;
context.cmd_id = GLOBAL_CMD_ID_CLOSE_SESSION;
params.flags = TC_CALL_GLOBAL | TC_CALL_SYNC;
ret = tc_client_call(¶ms);
if (ret != 0)
tloge("close session failed, ret=0x%x\n", ret);
#ifdef CONFIG_REGISTER_SHAREDMEM
release_session_register_sharedmem(session);
#endif
kill_ion_by_uuid((struct tc_uuid *)context.uuid);
return ret;
}
static void close_session_in_service_list(struct tc_ns_dev_file *dev,
struct tc_ns_service *service)
{
struct tc_ns_session *tmp_session = NULL;
struct tc_ns_session *session = NULL;
int ret;
list_for_each_entry_safe(session, tmp_session,
&service->session_list, head) {
if (session->owner != dev)
continue;
ret = close_session(dev, session, service->uuid,
(unsigned int)UUID_LEN, session->session_id);
if (ret != 0)
tloge("close session smc failed when close fd!\n");
mutex_lock(&service->session_lock);
list_del(&session->head);
mutex_unlock(&service->session_lock);
put_session_struct(session);
}
}
static bool if_exist_unclosed_session(struct tc_ns_dev_file *dev)
{
uint32_t index;
for (index = 0; index < SERVICES_MAX_COUNT; index++) {
if (dev->services[index] != NULL &&
list_empty(&dev->services[index]->session_list) == 0)
return true;
}
return false;
}
static int close_session_thread_fn(void *arg)
{
struct tc_ns_dev_file *dev = arg;
uint32_t index;
struct tc_ns_service *service = NULL;
for (index = 0; index < SERVICES_MAX_COUNT; index++) {
if (dev->services[index] != NULL &&
list_empty(&dev->services[index]->session_list) == 0) {
service = dev->services[index];
mutex_lock(&service->operation_lock);
close_session_in_service_list(dev, service);
mutex_unlock(&service->operation_lock);
put_service_struct(service);
}
}
tlogd("complete close all unclosed session\n");
complete(&dev->close_comp);
return 0;
}
void close_unclosed_session_in_kthread(struct tc_ns_dev_file *dev)
{
struct task_struct *close_thread = NULL;
if (!dev) {
tloge("dev is invalid\n");
return;
}
if (!if_exist_unclosed_session(dev))
return;
if (is_tee_rebooting())
return;
#ifndef CONFIG_TA_AFFINITY
close_session_thread_fn(dev);
(void)close_thread;
#else
if (is_ccos()) {
* In CCOS, TA's affinity can be set just in TEE, and CA affinity does not influence TA affiity,
* so no need to go to tzdriver to set affinity.
*/
close_session_thread_fn(dev);
} else {
close_thread = kthread_create(close_session_thread_fn,
dev, "close_fn_%6d", dev->dev_file_id);
if (unlikely(IS_ERR_OR_NULL(close_thread))) {
tloge("fail to create close session thread\n");
return;
}
tz_kthread_bind_mask(close_thread);
wake_up_process(close_thread);
wait_for_completion(&dev->close_comp);
tlogd("wait for completion success\n");
}
#endif
}
int tc_ns_close_session(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context)
{
int ret = -EINVAL;
struct tc_ns_service *service = NULL;
struct tc_ns_session *session = NULL;
if (!dev_file || !context) {
tloge("invalid dev_file or context\n");
return ret;
}
if (is_tee_rebooting()) {
context->returns.code = TEE_ERROR_IS_DEAD;
return TEE_ERROR_IS_DEAD;
}
service = get_service(dev_file, context);
if (!service) {
tloge("invalid service\n");
return ret;
}
* session_id in tee is unique, but in concurrency scene
* same session_id may appear in tzdriver, put session_list
* add/del in service->operation_lock can avoid it.
*/
if (lock_interruptible(&service->operation_lock) != 0) {
put_service_struct(service);
return -EINTR;
}
session = get_session(service, dev_file, context);
if (session) {
int ret2;
mutex_lock(&session->ta_session_lock);
ret2 = close_session(dev_file, session, context->uuid,
(unsigned int)UUID_LEN, context->session_id);
mutex_unlock(&session->ta_session_lock);
if (ret2 != 0)
tloge("close session smc failed!\n");
mutex_lock(&service->session_lock);
list_del(&session->head);
mutex_unlock(&service->session_lock);
put_session_struct(session);
put_session_struct(session);
ret = 0;
mutex_lock(&dev_file->service_lock);
del_service_from_dev(dev_file, service);
mutex_unlock(&dev_file->service_lock);
} else {
tloge("invalid session\n");
}
mutex_unlock(&service->operation_lock);
put_service_struct(service);
return ret;
}
static int check_param_types(struct tc_ns_client_context *context)
{
int index;
for (index = 0; index < TEE_PARAM_NUM; index++) {
uint32_t param_type = teec_param_type_get(context->param_types, index);
if (param_type == TEEC_MEMREF_REGISTER_INOUT) {
tloge("invoke should not with register shm\n");
return -EINVAL;
}
}
return 0;
}
int tc_ns_send_cmd(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context)
{
int ret = -EINVAL;
struct tc_ns_service *service = NULL;
struct tc_ns_session *session = NULL;
struct tc_call_params params = {
dev_file, context, NULL, 0
};
if (!dev_file || !context || (check_param_types(context) != 0)) {
tloge("invalid dev_file or context or param_types\n");
return ret;
}
if (is_tee_rebooting()) {
context->returns.code = TEE_ERROR_IS_DEAD;
return EFAULT;
}
service = get_service(dev_file, context);
if (service) {
session = get_session(service, dev_file, context);
put_service_struct(service);
if (session) {
tlogd("send cmd find session id %x\n",
context->session_id);
goto find_session;
}
tloge("can't find session\n");
} else {
tloge("can't find service\n");
}
return ret;
find_session:
mutex_lock(&session->ta_session_lock);
params.sess = session;
ret = tc_client_call(¶ms);
mutex_unlock(&session->ta_session_lock);
put_session_struct(session);
if (ret != 0)
tloge("smc call returns error, ret=0x%x\n", ret);
return ret;
}
static int ioctl_session_send_cmd(struct tc_ns_dev_file *dev_file,
struct tc_ns_client_context *context, void *argp)
{
int ret;
ret = tc_ns_send_cmd(dev_file, context);
if (ret != 0)
tloge("send cmd failed ret is %d\n", ret);
if (copy_to_user(argp, context, sizeof(*context)) != 0) {
if (ret == 0)
ret = -EFAULT;
}
return ret;
}
int tc_client_session_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -EINVAL;
void *argp = (void __user *)(uintptr_t)arg;
struct tc_ns_dev_file *dev_file = NULL;
struct tc_ns_client_context context;
if (!argp || !file) {
tloge("invalid params\n");
return -EINVAL;
}
dev_file = file->private_data;
if (copy_from_user(&context, argp, sizeof(context)) != 0) {
tloge("copy from user failed\n");
return -EFAULT;
}
context.returns.origin = TEEC_ORIGIN_COMMS;
switch (cmd) {
case TC_NS_CLIENT_IOCTL_SES_OPEN_REQ:
ret = tc_ns_open_session(dev_file, &context);
if (ret != 0)
tloge("open session failed ret is %d\n", ret);
if (copy_to_user(argp, &context, sizeof(context)) != 0 && ret == 0)
ret = -EFAULT;
break;
case TC_NS_CLIENT_IOCTL_SES_CLOSE_REQ:
ret = tc_ns_close_session(dev_file, &context);
break;
case TC_NS_CLIENT_IOCTL_SEND_CMD_REQ:
tee_trace_add_event(INVOKE_CMD_START, 0);
ret = ioctl_session_send_cmd(dev_file, &context, argp);
tee_trace_add_event(INVOKE_CMD_END, 0);
break;
default:
tloge("invalid cmd:0x%x!\n", cmd);
return ret;
}
* Don't leak ERESTARTSYS to user space.
*
* CloseSession is not reentrant, so convert to -EINTR.
* In other case, restart_syscall().
*
* It is better to call it right after the error code
* is generated (in tc_client_call), but kernel CAs are
* still exist when these words are written. Setting TIF
* flags for callers of those CAs is very hard to analysis.
*
* For kernel CA, when ERESTARTSYS is seen, loop in kernel
* instead of notifying user.
*
* P.S. ret code in this function is in mixed naming space.
* See the definition of ret. However, this function never
* return its default value, so using -EXXX is safe.
*/
if (ret == -ERESTARTSYS) {
if (cmd == TC_NS_CLIENT_IOCTL_SES_CLOSE_REQ)
ret = -EINTR;
else
return restart_syscall();
}
return ret;
}
static void cleanup_session(struct tc_ns_service *service)
{
struct tc_ns_session *session = NULL;
struct tc_ns_session *session_tmp = NULL;
if (!service)
return;
if (list_empty(&service->session_list) == 0) {
mutex_lock(&service->operation_lock);
list_for_each_entry_safe(session, session_tmp, &service->session_list, head) {
tlogd("clean up session %u\n", session->session_id);
mutex_lock(&service->session_lock);
list_del(&session->head);
mutex_unlock(&service->session_lock);
put_session_struct(session);
}
mutex_unlock(&service->operation_lock);
}
put_service_struct(service);
return;
}
void free_all_session(void)
{
struct tc_ns_dev_file *dev_file = NULL;
struct tc_ns_dev_file *dev_file_tmp = NULL;
struct tc_ns_dev_list *dev_list = NULL;
int i;
dev_list = get_dev_list();
if (!dev_list) {
tloge("cleanup session, dev list is null\n");
return;
}
mutex_lock(&dev_list->dev_lock);
list_for_each_entry_safe(dev_file, dev_file_tmp, &dev_list->dev_file_list, head) {
mutex_lock(&dev_file->service_lock);
for (i = 0; i < SERVICES_MAX_COUNT; i++) {
if (dev_file->services[i] == NULL)
continue;
get_service_struct(dev_file->services[i]);
mutex_unlock(&dev_file->service_lock);
cleanup_session(dev_file->services[i]);
mutex_lock(&dev_file->service_lock);
dev_file->services[i] = NULL;
}
mutex_unlock(&dev_file->service_lock);
}
mutex_unlock(&dev_list->dev_lock);
return;
}