blob: 2eb423855f1d5468bf035283b019d1db72355680 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2014, Linaro Limited
*/
#include <arm.h>
#include <assert.h>
#include <kernel/abort.h>
#include <kernel/misc.h>
#include <kernel/panic.h>
#include <kernel/tee_ta_manager.h>
#include <kernel/thread.h>
#include <kernel/trace_ta.h>
#include <kernel/user_ta.h>
#include <mm/tee_mmu.h>
#include <string.h>
#include <tee/tee_svc.h>
#include <tee/arch_svc.h>
#include <tee/tee_svc_cryp.h>
#include <tee/tee_svc_storage.h>
#include <tee/svc_cache.h>
#include <tee_syscall_numbers.h>
#include <trace.h>
#include <util.h>
#include "arch_svc_private.h"
#if (TRACE_LEVEL == TRACE_FLOW) && defined(CFG_TEE_CORE_TA_TRACE)
#define TRACE_SYSCALLS
#endif
struct syscall_entry {
syscall_t fn;
#ifdef TRACE_SYSCALLS
const char *name;
#endif
};
#ifdef TRACE_SYSCALLS
#define SYSCALL_ENTRY(_fn) { .fn = (syscall_t)_fn, .name = #_fn }
#else
#define SYSCALL_ENTRY(_fn) { .fn = (syscall_t)_fn }
#endif
/*
* This array is ordered according to the SYSCALL ids TEE_SCN_xxx
*/
static const struct syscall_entry tee_svc_syscall_table[] = {
SYSCALL_ENTRY(syscall_sys_return),
SYSCALL_ENTRY(syscall_log),
SYSCALL_ENTRY(syscall_panic),
SYSCALL_ENTRY(syscall_get_property),
SYSCALL_ENTRY(syscall_get_property_name_to_index),
SYSCALL_ENTRY(syscall_open_ta_session),
SYSCALL_ENTRY(syscall_close_ta_session),
SYSCALL_ENTRY(syscall_invoke_ta_command),
SYSCALL_ENTRY(syscall_check_access_rights),
SYSCALL_ENTRY(syscall_get_cancellation_flag),
SYSCALL_ENTRY(syscall_unmask_cancellation),
SYSCALL_ENTRY(syscall_mask_cancellation),
SYSCALL_ENTRY(syscall_wait),
SYSCALL_ENTRY(syscall_get_time),
SYSCALL_ENTRY(syscall_set_ta_time),
SYSCALL_ENTRY(syscall_cryp_state_alloc),
SYSCALL_ENTRY(syscall_cryp_state_copy),
SYSCALL_ENTRY(syscall_cryp_state_free),
SYSCALL_ENTRY(syscall_hash_init),
SYSCALL_ENTRY(syscall_hash_update),
SYSCALL_ENTRY(syscall_hash_final),
SYSCALL_ENTRY(syscall_cipher_init),
SYSCALL_ENTRY(syscall_cipher_update),
SYSCALL_ENTRY(syscall_cipher_final),
SYSCALL_ENTRY(syscall_cryp_obj_get_info),
SYSCALL_ENTRY(syscall_cryp_obj_restrict_usage),
SYSCALL_ENTRY(syscall_cryp_obj_get_attr),
SYSCALL_ENTRY(syscall_cryp_obj_alloc),
SYSCALL_ENTRY(syscall_cryp_obj_close),
SYSCALL_ENTRY(syscall_cryp_obj_reset),
SYSCALL_ENTRY(syscall_cryp_obj_populate),
SYSCALL_ENTRY(syscall_cryp_obj_copy),
SYSCALL_ENTRY(syscall_cryp_derive_key),
SYSCALL_ENTRY(syscall_cryp_random_number_generate),
SYSCALL_ENTRY(syscall_authenc_init),
SYSCALL_ENTRY(syscall_authenc_update_aad),
SYSCALL_ENTRY(syscall_authenc_update_payload),
SYSCALL_ENTRY(syscall_authenc_enc_final),
SYSCALL_ENTRY(syscall_authenc_dec_final),
SYSCALL_ENTRY(syscall_asymm_operate),
SYSCALL_ENTRY(syscall_asymm_verify),
SYSCALL_ENTRY(syscall_storage_obj_open),
SYSCALL_ENTRY(syscall_storage_obj_create),
SYSCALL_ENTRY(syscall_storage_obj_del),
SYSCALL_ENTRY(syscall_storage_obj_rename),
SYSCALL_ENTRY(syscall_storage_alloc_enum),
SYSCALL_ENTRY(syscall_storage_free_enum),
SYSCALL_ENTRY(syscall_storage_reset_enum),
SYSCALL_ENTRY(syscall_storage_start_enum),
SYSCALL_ENTRY(syscall_storage_next_enum),
SYSCALL_ENTRY(syscall_storage_obj_read),
SYSCALL_ENTRY(syscall_storage_obj_write),
SYSCALL_ENTRY(syscall_storage_obj_trunc),
SYSCALL_ENTRY(syscall_storage_obj_seek),
SYSCALL_ENTRY(syscall_obj_generate_key),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_not_supported),
SYSCALL_ENTRY(syscall_cache_operation),
};
#ifdef TRACE_SYSCALLS
static void trace_syscall(size_t num)
{
if (num == TEE_SCN_RETURN || num > TEE_SCN_MAX)
return;
FMSG("syscall #%zu (%s)", num, tee_svc_syscall_table[num].name);
}
#else
static void trace_syscall(size_t num __unused)
{
}
#endif
#ifdef CFG_SYSCALL_FTRACE
static void __noprof ftrace_syscall_enter(size_t num)
{
struct tee_ta_session *s = NULL;
/*
* Syscalls related to inter-TA communication can't be traced in the
* caller TA's ftrace buffer as it involves context switching to callee
* TA's context. Moreover, user can enable ftrace for callee TA to dump
* function trace in corresponding ftrace buffer.
*/
if (num == TEE_SCN_OPEN_TA_SESSION || num == TEE_SCN_CLOSE_TA_SESSION ||
num == TEE_SCN_INVOKE_TA_COMMAND)
return;
s = TAILQ_FIRST(&thread_get_tsd()->sess_stack);
if (!s)
return;
if (s->fbuf)
s->fbuf->syscall_trace_enabled = true;
}
static void __noprof ftrace_syscall_leave(void)
{
struct tee_ta_session *s = TAILQ_FIRST(&thread_get_tsd()->sess_stack);
if (!s)
return;
if (s->fbuf)
s->fbuf->syscall_trace_enabled = false;
}
#else
static void __noprof ftrace_syscall_enter(size_t num __unused)
{
}
static void __noprof ftrace_syscall_leave(void)
{
}
#endif
#ifdef ARM32
static void get_scn_max_args(struct thread_svc_regs *regs, size_t *scn,
size_t *max_args)
{
*scn = regs->r7;
*max_args = regs->r6;
}
#endif /*ARM32*/
#ifdef ARM64
static void get_scn_max_args(struct thread_svc_regs *regs, size_t *scn,
size_t *max_args)
{
if (((regs->spsr >> SPSR_MODE_RW_SHIFT) & SPSR_MODE_RW_MASK) ==
SPSR_MODE_RW_32) {
*scn = regs->x7;
*max_args = regs->x6;
} else {
*scn = regs->x8;
*max_args = 0;
}
}
#endif /*ARM64*/
#ifdef ARM32
static void set_svc_retval(struct thread_svc_regs *regs, uint32_t ret_val)
{
regs->r0 = ret_val;
}
#endif /*ARM32*/
#ifdef ARM64
static void set_svc_retval(struct thread_svc_regs *regs, uint64_t ret_val)
{
regs->x0 = ret_val;
}
#endif /*ARM64*/
bool user_ta_handle_svc(struct thread_svc_regs *regs)
{
size_t scn;
size_t max_args;
syscall_t scf;
COMPILE_TIME_ASSERT(ARRAY_SIZE(tee_svc_syscall_table) ==
(TEE_SCN_MAX + 1));
get_scn_max_args(regs, &scn, &max_args);
trace_syscall(scn);
if (max_args > TEE_SVC_MAX_ARGS) {
DMSG("Too many arguments for SCN %zu (%zu)", scn, max_args);
set_svc_retval(regs, TEE_ERROR_GENERIC);
return true; /* return to user mode */
}
if (scn > TEE_SCN_MAX)
scf = (syscall_t)syscall_not_supported;
else
scf = tee_svc_syscall_table[scn].fn;
ftrace_syscall_enter(scn);
set_svc_retval(regs, tee_svc_do_call(regs, scf));
ftrace_syscall_leave();
/*
* Return true if we're to return to user mode,
* thread_svc_handler() will take care of the rest.
*/
return scn != TEE_SCN_RETURN && scn != TEE_SCN_PANIC;
}
#define TA32_CONTEXT_MAX_SIZE (14 * sizeof(uint32_t))
#define TA64_CONTEXT_MAX_SIZE (2 * sizeof(uint64_t))
#ifdef ARM32
#ifdef CFG_UNWIND
/* Get register values pushed onto the stack by utee_panic() */
static void save_panic_regs_a32_ta(struct thread_specific_data *tsd,
uint32_t *pushed)
{
tsd->abort_regs = (struct thread_abort_regs){
.elr = pushed[0],
.r0 = pushed[1],
.r1 = pushed[2],
.r2 = pushed[3],
.r3 = pushed[4],
.r4 = pushed[5],
.r5 = pushed[6],
.r6 = pushed[7],
.r7 = pushed[8],
.r8 = pushed[9],
.r9 = pushed[10],
.r10 = pushed[11],
.r11 = pushed[12],
.usr_sp = (uint32_t)pushed,
.usr_lr = pushed[13],
.spsr = read_spsr(),
};
}
static void save_panic_stack(struct thread_svc_regs *regs)
{
struct thread_specific_data *tsd = thread_get_tsd();
struct tee_ta_session *s;
if (tee_ta_get_current_session(&s))
panic("No current session");
if (tee_mmu_check_access_rights(&to_user_ta_ctx(s->ctx)->uctx,
TEE_MEMORY_ACCESS_READ |
TEE_MEMORY_ACCESS_WRITE,
(uaddr_t)regs->r1,
TA32_CONTEXT_MAX_SIZE)) {
TAMSG_RAW("");
TAMSG_RAW("Can't unwind invalid user stack 0x%" PRIxUA,
(uaddr_t)regs->r1);
return;
}
tsd->abort_type = ABORT_TYPE_TA_PANIC;
tsd->abort_descr = 0;
tsd->abort_va = 0;
save_panic_regs_a32_ta(tsd, (uint32_t *)regs->r1);
}
#else /* CFG_UNWIND */
static void save_panic_stack(struct thread_svc_regs *regs __unused)
{
struct thread_specific_data *tsd = thread_get_tsd();
tsd->abort_type = ABORT_TYPE_TA_PANIC;
}
#endif
#endif /*ARM32*/
#ifdef ARM64
#ifdef CFG_UNWIND
/* Get register values pushed onto the stack by utee_panic() (32-bit TA) */
static void save_panic_regs_a32_ta(struct thread_specific_data *tsd,
uint32_t *pushed)
{
tsd->abort_regs = (struct thread_abort_regs){
.elr = pushed[0],
.x0 = pushed[1],
.x1 = pushed[2],
.x2 = pushed[3],
.x3 = pushed[4],
.x4 = pushed[5],
.x5 = pushed[6],
.x6 = pushed[7],
.x7 = pushed[8],
.x8 = pushed[9],
.x9 = pushed[10],
.x10 = pushed[11],
.x11 = pushed[12],
.x13 = (uint64_t)pushed,
.x14 = pushed[13],
.spsr = (SPSR_MODE_RW_32 << SPSR_MODE_RW_SHIFT),
};
}
/* Get register values pushed onto the stack by utee_panic() (64-bit TA) */
static void save_panic_regs_a64_ta(struct thread_specific_data *tsd,
uint64_t *pushed)
{
tsd->abort_regs = (struct thread_abort_regs){
.x29 = pushed[0],
.elr = pushed[1],
.spsr = (SPSR_64_MODE_EL0 << SPSR_64_MODE_EL_SHIFT),
};
}
static void save_panic_stack(struct thread_svc_regs *regs)
{
struct thread_specific_data *tsd = thread_get_tsd();
struct tee_ta_session *s = NULL;
struct user_ta_ctx *utc = NULL;
if (tee_ta_get_current_session(&s) != TEE_SUCCESS)
panic();
utc = to_user_ta_ctx(s->ctx);
if (tee_mmu_check_access_rights(&utc->uctx, TEE_MEMORY_ACCESS_READ |
TEE_MEMORY_ACCESS_WRITE,
(uaddr_t)regs->x1,
utc->is_32bit ?
TA32_CONTEXT_MAX_SIZE :
TA64_CONTEXT_MAX_SIZE)) {
TAMSG_RAW("");
TAMSG_RAW("Can't unwind invalid user stack 0x%" PRIxUA,
(uaddr_t)regs->x1);
return;
}
tsd->abort_type = ABORT_TYPE_TA_PANIC;
tsd->abort_descr = 0;
tsd->abort_va = 0;
if (utc->is_32bit)
save_panic_regs_a32_ta(tsd, (uint32_t *)regs->x1);
else
save_panic_regs_a64_ta(tsd, (uint64_t *)regs->x1);
}
#else /* CFG_UNWIND */
static void save_panic_stack(struct thread_svc_regs *regs __unused)
{
struct thread_specific_data *tsd = thread_get_tsd();
tsd->abort_type = ABORT_TYPE_TA_PANIC;
}
#endif /* CFG_UNWIND */
#endif /*ARM64*/
uint32_t tee_svc_sys_return_helper(uint32_t ret, bool panic,
uint32_t panic_code,
struct thread_svc_regs *regs)
{
if (panic) {
TAMSG_RAW("");
TAMSG_RAW("TA panicked with code 0x%" PRIx32, panic_code);
save_panic_stack(regs);
}
#ifdef ARM32
regs->r1 = panic;
regs->r2 = panic_code;
#endif
#ifdef ARM64
regs->x1 = panic;
regs->x2 = panic_code;
#endif
return ret;
}